mixnet/core/
sessions.rs

1// Copyright 2022 Parity Technologies (UK) Ltd.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the "Software"),
5// to deal in the Software without restriction, including without limitation
6// the rights to use, copy, modify, merge, publish, distribute, sublicense,
7// and/or sell copies of the Software, and to permit persons to whom the
8// Software is furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19// DEALINGS IN THE SOFTWARE.
20
21//! Mixnet sessions.
22
23use super::{
24	kx_pair::KxPair, packet_queues::AuthoredPacketQueue, replay_filter::ReplayFilter,
25	topology::Topology,
26};
27use std::{
28	fmt,
29	ops::{Add, Index, IndexMut},
30	time::Duration,
31};
32
33pub struct Session<X> {
34	/// Key-exchange key pair.
35	pub kx_pair: KxPair,
36	/// Mixnode topology.
37	pub topology: Topology<X>,
38	/// Queue of packets authored by us, to be dispatched in place of drop cover traffic.
39	pub authored_packet_queue: AuthoredPacketQueue,
40	/// See [`SessionConfig`](super::config::SessionConfig::mean_authored_packet_period).
41	pub mean_authored_packet_period: Duration,
42	/// Filter applied to incoming packets to prevent replay. This is per-session because the
43	/// key-exchange keys are rotated every session. Note that while this always exists, for
44	/// sessions where we are not a mixnode, it should never contain anything, and so should not
45	/// cost anything ([`ReplayFilter`] lazily allocates internally).
46	pub replay_filter: ReplayFilter,
47}
48
49/// Absolute session index.
50pub type SessionIndex = u32;
51
52/// Relative session index.
53#[derive(Clone, Copy, PartialEq, Eq)]
54pub enum RelSessionIndex {
55	/// The current session.
56	Current,
57	/// The previous session.
58	Prev,
59}
60
61impl RelSessionIndex {
62	/// Returns the `RelSessionIndex` corresponding to `session_index`, or `None` if there is no
63	/// such `RelSessionIndex`.
64	pub fn from_session_index(
65		session_index: SessionIndex,
66		current_session_index: SessionIndex,
67	) -> Option<Self> {
68		match current_session_index.checked_sub(session_index) {
69			Some(0) => Some(Self::Current),
70			Some(1) => Some(Self::Prev),
71			_ => None,
72		}
73	}
74}
75
76impl Add<SessionIndex> for RelSessionIndex {
77	type Output = SessionIndex;
78
79	fn add(self, other: SessionIndex) -> Self::Output {
80		match self {
81			Self::Current => other,
82			Self::Prev => other.checked_sub(1).expect("Session index underflow"),
83		}
84	}
85}
86
87pub enum SessionSlot<X> {
88	Empty,
89	KxPair(KxPair),
90	/// Like [`Empty`](Self::Empty), but we should not try to create a [`Session`] struct.
91	Disabled,
92	Full(Session<X>),
93}
94
95impl<X> SessionSlot<X> {
96	pub fn is_empty(&self) -> bool {
97		matches!(self, Self::Empty)
98	}
99
100	pub fn as_option(&self) -> Option<&Session<X>> {
101		match self {
102			Self::Full(session) => Some(session),
103			_ => None,
104		}
105	}
106
107	pub fn as_mut_option(&mut self) -> Option<&mut Session<X>> {
108		match self {
109			Self::Full(session) => Some(session),
110			_ => None,
111		}
112	}
113}
114
115pub struct Sessions<X> {
116	pub current: SessionSlot<X>,
117	pub prev: SessionSlot<X>,
118}
119
120impl<X> Sessions<X> {
121	pub fn is_empty(&self) -> bool {
122		self.current.is_empty() && self.prev.is_empty()
123	}
124
125	pub fn iter(&self) -> impl Iterator<Item = &Session<X>> {
126		[&self.current, &self.prev]
127			.into_iter()
128			.filter_map(|session| session.as_option())
129	}
130
131	/// This is guaranteed to return the current session first, if it exists.
132	pub fn enumerate_mut(&mut self) -> impl Iterator<Item = (RelSessionIndex, &mut Session<X>)> {
133		[(RelSessionIndex::Current, &mut self.current), (RelSessionIndex::Prev, &mut self.prev)]
134			.into_iter()
135			.filter_map(|(index, session)| session.as_mut_option().map(|session| (index, session)))
136	}
137}
138
139impl<X> Index<RelSessionIndex> for Sessions<X> {
140	type Output = SessionSlot<X>;
141
142	fn index(&self, index: RelSessionIndex) -> &Self::Output {
143		match index {
144			RelSessionIndex::Current => &self.current,
145			RelSessionIndex::Prev => &self.prev,
146		}
147	}
148}
149
150impl<X> IndexMut<RelSessionIndex> for Sessions<X> {
151	fn index_mut(&mut self, index: RelSessionIndex) -> &mut Self::Output {
152		match index {
153			RelSessionIndex::Current => &mut self.current,
154			RelSessionIndex::Prev => &mut self.prev,
155		}
156	}
157}
158
159/// Each session should progress through these phases in order.
160#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
161pub enum SessionPhase {
162	/// Generate cover traffic to the current session's mixnode set.
163	CoverToCurrent,
164	/// Build requests using the current session's mixnode set. The previous session's mixnode set
165	/// may be used if this is explicitly requested.
166	RequestsToCurrent,
167	/// Only send cover (and forwarded) traffic to the previous session's mixnode set. Any packets
168	/// in the authored packet queue for the previous session at this point are effectively
169	/// dropped.
170	CoverToPrev,
171	/// Disconnect the previous session's mixnode set.
172	DisconnectFromPrev,
173}
174
175impl SessionPhase {
176	/// Is the previous session still needed?
177	pub fn need_prev(self) -> bool {
178		self < Self::DisconnectFromPrev
179	}
180
181	/// Should we allow pushing to and popping from the authored packet queue for the specified
182	/// session?
183	pub fn allow_requests_and_replies(self, rel_session_index: RelSessionIndex) -> bool {
184		match rel_session_index {
185			RelSessionIndex::Prev => self < Self::CoverToPrev,
186			RelSessionIndex::Current => self >= Self::RequestsToCurrent,
187		}
188	}
189
190	/// Which session should requests be built for by default?
191	pub fn default_request_session(self) -> RelSessionIndex {
192		if self >= Self::RequestsToCurrent {
193			RelSessionIndex::Current
194		} else {
195			RelSessionIndex::Prev
196		}
197	}
198}
199
200impl fmt::Display for SessionPhase {
201	fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
202		match self {
203			Self::CoverToCurrent => write!(fmt, "Generating cover traffic to current mixnode set"),
204			Self::RequestsToCurrent => write!(fmt, "Building requests using current mixnode set"),
205			Self::CoverToPrev => write!(fmt, "Only sending cover traffic to previous mixnode set"),
206			Self::DisconnectFromPrev => write!(fmt, "Only using current mixnode set"),
207		}
208	}
209}
210
211/// The index and phase of the current session.
212#[derive(Clone, Copy, PartialEq, Eq)]
213pub struct SessionStatus {
214	/// Index of the current session.
215	pub current_index: SessionIndex,
216	/// Current session phase.
217	pub phase: SessionPhase,
218}
219
220impl fmt::Display for SessionStatus {
221	fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
222		write!(fmt, "Current index {}, phase: {}", self.current_index, self.phase)
223	}
224}