aboutsummaryrefslogtreecommitdiff
path: root/src/cid.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/cid.rs')
-rw-r--r--src/cid.rs964
1 files changed, 964 insertions, 0 deletions
diff --git a/src/cid.rs b/src/cid.rs
new file mode 100644
index 0000000..2584635
--- /dev/null
+++ b/src/cid.rs
@@ -0,0 +1,964 @@
+// Copyright (C) 2022, Cloudflare, Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use crate::Error;
+use crate::Result;
+
+use crate::frame;
+use crate::packet::ConnectionId;
+
+use std::collections::VecDeque;
+
+/// A structure holding a `ConnectionId` and all its related metadata.
+#[derive(Debug, Default)]
+pub struct ConnectionIdEntry {
+ /// The Connection ID.
+ pub cid: ConnectionId<'static>,
+
+ /// Its associated sequence number.
+ pub seq: u64,
+
+ /// Its associated reset token. Initial CIDs may not have any reset token.
+ pub reset_token: Option<u128>,
+
+ /// The path identifier using this CID, if any.
+ pub path_id: Option<usize>,
+}
+
+#[derive(Default)]
+struct BoundedNonEmptyConnectionIdVecDeque {
+ /// The inner `VecDeque`.
+ inner: VecDeque<ConnectionIdEntry>,
+
+ /// The maximum number of elements that the `VecDeque` can have.
+ capacity: usize,
+}
+
+impl BoundedNonEmptyConnectionIdVecDeque {
+ /// Creates a `VecDeque` bounded by `capacity` and inserts
+ /// `initial_entry` in it.
+ fn new(capacity: usize, initial_entry: ConnectionIdEntry) -> Self {
+ let mut inner = VecDeque::with_capacity(1);
+ inner.push_back(initial_entry);
+ Self { inner, capacity }
+ }
+
+ /// Updates the maximum capacity of the inner `VecDeque` to `new_capacity`.
+ /// Does nothing if `new_capacity` is lower or equal to the current
+ /// `capacity`.
+ fn resize(&mut self, new_capacity: usize) {
+ if new_capacity > self.capacity {
+ self.capacity = new_capacity;
+ }
+ }
+
+ /// Returns the oldest inserted entry still present in the `VecDeque`.
+ fn get_oldest(&self) -> &ConnectionIdEntry {
+ self.inner.front().expect("vecdeque is empty")
+ }
+
+ /// Gets a immutable reference to the entry having the provided `seq`.
+ fn get(&self, seq: u64) -> Option<&ConnectionIdEntry> {
+ // We need to iterate over the whole map to find the key.
+ self.inner.iter().find(|e| e.seq == seq)
+ }
+
+ /// Gets a mutable reference to the entry having the provided `seq`.
+ fn get_mut(&mut self, seq: u64) -> Option<&mut ConnectionIdEntry> {
+ // We need to iterate over the whole map to find the key.
+ self.inner.iter_mut().find(|e| e.seq == seq)
+ }
+
+ /// Returns an iterator over the entries in the `VecDeque`.
+ fn iter(&self) -> impl Iterator<Item = &ConnectionIdEntry> {
+ self.inner.iter()
+ }
+
+ /// Returns the number of elements in the `VecDeque`.
+ fn len(&self) -> usize {
+ self.inner.len()
+ }
+
+ /// Inserts the provided entry in the `VecDeque`.
+ ///
+ /// This method ensures the unicity of the `seq` associated to an entry. If
+ /// an entry has the same `seq` than `e`, this method updates the entry in
+ /// the `VecDeque` and the number of stored elements remains unchanged.
+ ///
+ /// If inserting a new element would exceed the collection's capacity, this
+ /// method raises an [`IdLimit`].
+ ///
+ /// [`IdLimit`]: enum.Error.html#IdLimit
+ fn insert(&mut self, e: ConnectionIdEntry) -> Result<()> {
+ // Ensure we don't have duplicates.
+ match self.get_mut(e.seq) {
+ Some(oe) => *oe = e,
+ None => {
+ if self.inner.len() == self.capacity {
+ return Err(Error::IdLimit);
+ }
+ self.inner.push_back(e);
+ },
+ };
+ Ok(())
+ }
+
+ /// Removes all the elements in the collection and inserts the provided one.
+ fn clear_and_insert(&mut self, e: ConnectionIdEntry) {
+ self.inner.clear();
+ self.inner.push_back(e);
+ }
+
+ /// Removes the element in the collection having the provided `seq`.
+ ///
+ /// If this method is called when there remains a single element in the
+ /// collection, this method raises an [`OutOfIdentifiers`].
+ ///
+ /// Returns `Some` if the element was in the collection and removed, or
+ /// `None` if it was not and nothing was modified.
+ ///
+ /// [`OutOfIdentifiers`]: enum.Error.html#OutOfIdentifiers
+ fn remove(&mut self, seq: u64) -> Result<Option<ConnectionIdEntry>> {
+ if self.inner.len() <= 1 {
+ return Err(Error::OutOfIdentifiers);
+ }
+
+ Ok(self
+ .inner
+ .iter()
+ .position(|e| e.seq == seq)
+ .and_then(|index| self.inner.remove(index)))
+ }
+
+ /// Removes all the elements in the collection whose `seq` is lower than the
+ /// provided one, and inserts `e` in the collection.
+ ///
+ /// For each removed element `re`, `f(re)` is called.
+ ///
+ /// If the inserted element has a `seq` lower than the one used to remove
+ /// elements, it raises an [`OutOfIdentifiers`].
+ ///
+ /// [`OutOfIdentifiers`]: enum.Error.html#OutOfIdentifiers
+ fn remove_lower_than_and_insert<F>(
+ &mut self, seq: u64, e: ConnectionIdEntry, mut f: F,
+ ) -> Result<()>
+ where
+ F: FnMut(&ConnectionIdEntry),
+ {
+ // The insert entry MUST have a sequence higher or equal to the ones
+ // being retired.
+ if e.seq < seq {
+ return Err(Error::OutOfIdentifiers);
+ }
+
+ // To avoid exceeding the capacity of the inner `VecDeque`, we first
+ // remove the elements and then insert the new one.
+ self.inner.retain(|e| {
+ if e.seq < seq {
+ f(e);
+ false
+ } else {
+ true
+ }
+ });
+
+ // Note that if no element has been retired and the `VecDeque` reaches
+ // its capacity limit, this will raise an `IdLimit`.
+ self.insert(e)
+ }
+}
+
+/// An iterator over QUIC Connection IDs.
+pub struct ConnectionIdIter {
+ cids: VecDeque<ConnectionId<'static>>,
+}
+
+impl Iterator for ConnectionIdIter {
+ type Item = ConnectionId<'static>;
+
+ #[inline]
+ fn next(&mut self) -> Option<Self::Item> {
+ self.cids.pop_front()
+ }
+}
+
+impl ExactSizeIterator for ConnectionIdIter {
+ #[inline]
+ fn len(&self) -> usize {
+ self.cids.len()
+ }
+}
+
+#[derive(Default)]
+pub struct ConnectionIdentifiers {
+ /// All the Destination Connection IDs provided by our peer.
+ dcids: BoundedNonEmptyConnectionIdVecDeque,
+
+ /// All the Source Connection IDs we provide to our peer.
+ scids: BoundedNonEmptyConnectionIdVecDeque,
+
+ /// Source Connection IDs that should be announced to the peer.
+ advertise_new_scid_seqs: VecDeque<u64>,
+
+ /// Retired Destination Connection IDs that should be announced to the peer.
+ retire_dcid_seqs: VecDeque<u64>,
+
+ /// Retired Source Connection IDs that should be notified to the
+ /// application.
+ retired_scids: VecDeque<ConnectionId<'static>>,
+
+ /// Largest "Retire Prior To" we received from the peer.
+ largest_peer_retire_prior_to: u64,
+
+ /// Largest sequence number we received from the peer.
+ largest_destination_seq: u64,
+
+ /// Next sequence number to use.
+ next_scid_seq: u64,
+
+ /// "Retire Prior To" value to advertise to the peer.
+ retire_prior_to: u64,
+
+ /// The maximum number of source Connection IDs our peer allows us.
+ source_conn_id_limit: usize,
+
+ /// Does the host use zero-length source Connection ID.
+ zero_length_scid: bool,
+
+ /// Does the host use zero-length destination Connection ID.
+ zero_length_dcid: bool,
+}
+
+impl ConnectionIdentifiers {
+ /// Creates a new `ConnectionIdentifiers` with the specified destination
+ /// connection ID limit and initial source Connection ID. The destination
+ /// Connection ID is set to the empty one.
+ pub fn new(
+ mut destination_conn_id_limit: usize, initial_scid: &ConnectionId,
+ initial_path_id: usize, reset_token: Option<u128>,
+ ) -> ConnectionIdentifiers {
+ // It must be at least 2.
+ if destination_conn_id_limit < 2 {
+ destination_conn_id_limit = 2;
+ }
+
+ // Initially, the limit of active source connection IDs is 2.
+ let source_conn_id_limit = 2;
+
+ // Record the zero-length SCID status.
+ let zero_length_scid = initial_scid.is_empty();
+
+ let initial_scid =
+ ConnectionId::from_ref(initial_scid.as_ref()).into_owned();
+
+ // We need to track up to (2 * source_conn_id_limit - 1) source
+ // Connection IDs when the host wants to force their renewal.
+ let scids = BoundedNonEmptyConnectionIdVecDeque::new(
+ 2 * source_conn_id_limit - 1,
+ ConnectionIdEntry {
+ cid: initial_scid,
+ seq: 0,
+ reset_token,
+ path_id: Some(initial_path_id),
+ },
+ );
+
+ let dcids = BoundedNonEmptyConnectionIdVecDeque::new(
+ destination_conn_id_limit,
+ ConnectionIdEntry {
+ cid: ConnectionId::default(),
+ seq: 0,
+ reset_token: None,
+ path_id: Some(initial_path_id),
+ },
+ );
+
+ // Because we already inserted the initial SCID.
+ let next_scid_seq = 1;
+ ConnectionIdentifiers {
+ scids,
+ dcids,
+ retired_scids: VecDeque::new(),
+ next_scid_seq,
+ source_conn_id_limit,
+ zero_length_scid,
+ ..Default::default()
+ }
+ }
+
+ /// Sets the maximum number of source connection IDs our peer allows us.
+ pub fn set_source_conn_id_limit(&mut self, v: u64) {
+ // Bound conn id limit so our scids queue sizing is valid.
+ let v = std::cmp::min(v, (usize::MAX / 2) as u64) as usize;
+
+ // It must be at least 2.
+ if v >= 2 {
+ self.source_conn_id_limit = v;
+ // We need to track up to (2 * source_conn_id_limit - 1) source
+ // Connection IDs when the host wants to force their renewal.
+ self.scids.resize(2 * v - 1);
+ }
+ }
+
+ /// Gets the destination Connection ID associated with the provided sequence
+ /// number.
+ #[inline]
+ pub fn get_dcid(&self, seq_num: u64) -> Result<&ConnectionIdEntry> {
+ self.dcids.get(seq_num).ok_or(Error::InvalidState)
+ }
+
+ /// Gets the source Connection ID associated with the provided sequence
+ /// number.
+ #[inline]
+ pub fn get_scid(&self, seq_num: u64) -> Result<&ConnectionIdEntry> {
+ self.scids.get(seq_num).ok_or(Error::InvalidState)
+ }
+
+ /// Adds a new source identifier, and indicates whether it should be
+ /// advertised through a `NEW_CONNECTION_ID` frame or not.
+ ///
+ /// At any time, the peer cannot have more Destination Connection IDs than
+ /// the maximum number of active Connection IDs it negotiated. In such case
+ /// (i.e., when [`active_source_cids()`] - `peer_active_conn_id_limit` = 0,
+ /// if the caller agrees to request the removal of previous connection IDs,
+ /// it sets the `retire_if_needed` parameter. Otherwise, an [`IdLimit`] is
+ /// returned.
+ ///
+ /// Note that setting `retire_if_needed` does not prevent this function from
+ /// returning an [`IdLimit`] in the case the caller wants to retire still
+ /// unannounced Connection IDs.
+ ///
+ /// When setting the initial Source Connection ID, the `reset_token` may be
+ /// `None`. However, other Source CIDs must have an associated
+ /// `reset_token`. Providing `None` as the `reset_token` for non-initial
+ /// SCIDs raises an [`InvalidState`].
+ ///
+ /// In the case the provided `cid` is already present, it does not add it.
+ /// If the provided `reset_token` differs from the one already registered,
+ /// returns an `InvalidState`.
+ ///
+ /// Returns the sequence number associated to that new source identifier.
+ ///
+ /// [`active_source_cids()`]: struct.ConnectionIdentifiers.html#method.active_source_cids
+ /// [`InvalidState`]: enum.Error.html#InvalidState
+ /// [`IdLimit`]: enum.Error.html#IdLimit
+ pub fn new_scid(
+ &mut self, cid: ConnectionId<'static>, reset_token: Option<u128>,
+ advertise: bool, path_id: Option<usize>, retire_if_needed: bool,
+ ) -> Result<u64> {
+ if self.zero_length_scid {
+ return Err(Error::InvalidState);
+ }
+
+ // Check whether the number of source Connection IDs does not exceed the
+ // limit. If the host agrees to retire old CIDs, it can store up to
+ // (2 * source_active_conn_id - 1) source CIDs. This limit is enforced
+ // when calling `self.scids.insert()`.
+ if self.scids.len() >= self.source_conn_id_limit {
+ if !retire_if_needed {
+ return Err(Error::IdLimit);
+ }
+
+ // We need to retire the lowest one.
+ self.retire_prior_to = self.lowest_usable_scid_seq()? + 1;
+ }
+
+ let seq = self.next_scid_seq;
+
+ if reset_token.is_none() && seq != 0 {
+ return Err(Error::InvalidState);
+ }
+
+ // Check first that the SCID has not been inserted before.
+ if let Some(e) = self.scids.iter().find(|e| e.cid == cid) {
+ if e.reset_token != reset_token {
+ return Err(Error::InvalidState);
+ }
+ return Ok(e.seq);
+ }
+
+ self.scids.insert(ConnectionIdEntry {
+ cid,
+ seq,
+ reset_token,
+ path_id,
+ })?;
+ self.next_scid_seq += 1;
+
+ self.mark_advertise_new_scid_seq(seq, advertise);
+
+ Ok(seq)
+ }
+
+ /// Sets the initial destination identifier.
+ pub fn set_initial_dcid(
+ &mut self, cid: ConnectionId<'static>, reset_token: Option<u128>,
+ path_id: Option<usize>,
+ ) {
+ // Record the zero-length DCID status.
+ self.zero_length_dcid = cid.is_empty();
+ self.dcids.clear_and_insert(ConnectionIdEntry {
+ cid,
+ seq: 0,
+ reset_token,
+ path_id,
+ });
+ }
+
+ /// Adds a new Destination Connection ID (originating from a
+ /// NEW_CONNECTION_ID frame) and process all its related metadata.
+ ///
+ /// Returns an error if the provided Connection ID or its metadata are
+ /// invalid.
+ ///
+ /// Returns a list of tuples (DCID sequence number, Path ID), containing the
+ /// sequence number of retired DCIDs that were linked to their respective
+ /// Path ID.
+ pub fn new_dcid(
+ &mut self, cid: ConnectionId<'static>, seq: u64, reset_token: u128,
+ retire_prior_to: u64,
+ ) -> Result<Vec<(u64, usize)>> {
+ if self.zero_length_dcid {
+ return Err(Error::InvalidState);
+ }
+
+ let mut retired_path_ids = Vec::new();
+ // If an endpoint receives a NEW_CONNECTION_ID frame that repeats a
+ // previously issued connection ID with a different Stateless Reset
+ // Token field value or a different Sequence Number field value, or if a
+ // sequence number is used for different connection IDs, the endpoint
+ // MAY treat that receipt as a connection error of type
+ // PROTOCOL_VIOLATION.
+ if let Some(e) = self.dcids.iter().find(|e| e.cid == cid || e.seq == seq)
+ {
+ if e.cid != cid || e.seq != seq || e.reset_token != Some(reset_token)
+ {
+ return Err(Error::InvalidFrame);
+ }
+ // The identifier is already there, nothing to do.
+ return Ok(retired_path_ids);
+ }
+
+ // The value in the Retire Prior To field MUST be less than or equal to
+ // the value in the Sequence Number field. Receiving a value in the
+ // Retire Prior To field that is greater than that in the Sequence
+ // Number field MUST be treated as a connection error of type
+ // FRAME_ENCODING_ERROR.
+ if retire_prior_to > seq {
+ return Err(Error::InvalidFrame);
+ }
+
+ // An endpoint that receives a NEW_CONNECTION_ID frame with a sequence
+ // number smaller than the Retire Prior To field of a previously
+ // received NEW_CONNECTION_ID frame MUST send a corresponding
+ // RETIRE_CONNECTION_ID frame that retires the newly received connection
+ // ID, unless it has already done so for that sequence number.
+ if seq < self.largest_peer_retire_prior_to &&
+ !self.retire_dcid_seqs.contains(&seq)
+ {
+ self.retire_dcid_seqs.push_back(seq);
+ return Ok(retired_path_ids);
+ }
+
+ if seq > self.largest_destination_seq {
+ self.largest_destination_seq = seq;
+ }
+
+ let new_entry = ConnectionIdEntry {
+ cid: cid.clone(),
+ seq,
+ reset_token: Some(reset_token),
+ path_id: None,
+ };
+
+ // A receiver MUST ignore any Retire Prior To fields that do not
+ // increase the largest received Retire Prior To value.
+ //
+ // After processing a NEW_CONNECTION_ID frame and adding and retiring
+ // active connection IDs, if the number of active connection IDs exceeds
+ // the value advertised in its active_connection_id_limit transport
+ // parameter, an endpoint MUST close the connection with an error of type
+ // CONNECTION_ID_LIMIT_ERROR.
+ if retire_prior_to > self.largest_peer_retire_prior_to {
+ let retired = &mut self.retire_dcid_seqs;
+ self.dcids.remove_lower_than_and_insert(
+ retire_prior_to,
+ new_entry,
+ |e| {
+ retired.push_back(e.seq);
+
+ if let Some(pid) = e.path_id {
+ retired_path_ids.push((e.seq, pid));
+ }
+ },
+ )?;
+ self.largest_peer_retire_prior_to = retire_prior_to;
+ } else {
+ self.dcids.insert(new_entry)?;
+ }
+
+ Ok(retired_path_ids)
+ }
+
+ /// Retires the Source Connection ID having the provided sequence number.
+ ///
+ /// In case the retired Connection ID is the same as the one used by the
+ /// packet requesting the retiring, or if the retired sequence number is
+ /// greater than any previously advertised sequence numbers, it returns an
+ /// [`InvalidState`].
+ ///
+ /// Returns the path ID that was associated to the retired CID, if any.
+ ///
+ /// [`InvalidState`]: enum.Error.html#InvalidState
+ pub fn retire_scid(
+ &mut self, seq: u64, pkt_dcid: &ConnectionId,
+ ) -> Result<Option<usize>> {
+ if seq >= self.next_scid_seq {
+ return Err(Error::InvalidState);
+ }
+
+ let pid = if let Some(e) = self.scids.remove(seq)? {
+ if e.cid == *pkt_dcid {
+ return Err(Error::InvalidState);
+ }
+
+ // Notifies the application.
+ self.retired_scids.push_back(e.cid);
+
+ // Retiring this SCID may increase the retire prior to.
+ let lowest_scid_seq = self.lowest_usable_scid_seq()?;
+ self.retire_prior_to = lowest_scid_seq;
+
+ e.path_id
+ } else {
+ None
+ };
+
+ Ok(pid)
+ }
+
+ /// Retires the Destination Connection ID having the provided sequence
+ /// number.
+ ///
+ /// If the caller tries to retire the last destination Connection ID, this
+ /// method triggers an [`OutOfIdentifiers`].
+ ///
+ /// If the caller tries to retire a non-existing Destination Connection
+ /// ID sequence number, this method returns an [`InvalidState`].
+ ///
+ /// Returns the path ID that was associated to the retired CID, if any.
+ ///
+ /// [`OutOfIdentifiers`]: enum.Error.html#OutOfIdentifiers
+ /// [`InvalidState`]: enum.Error.html#InvalidState
+ pub fn retire_dcid(&mut self, seq: u64) -> Result<Option<usize>> {
+ if self.zero_length_dcid {
+ return Err(Error::InvalidState);
+ }
+
+ let e = self.dcids.remove(seq)?.ok_or(Error::InvalidState)?;
+
+ self.retire_dcid_seqs.push_back(seq);
+
+ Ok(e.path_id)
+ }
+
+ /// Updates the Source Connection ID entry with the provided sequence number
+ /// to indicate that it is now linked to the provided path ID.
+ pub fn link_scid_to_path_id(
+ &mut self, dcid_seq: u64, path_id: usize,
+ ) -> Result<()> {
+ let e = self.scids.get_mut(dcid_seq).ok_or(Error::InvalidState)?;
+ e.path_id = Some(path_id);
+ Ok(())
+ }
+
+ /// Updates the Destination Connection ID entry with the provided sequence
+ /// number to indicate that it is now linked to the provided path ID.
+ pub fn link_dcid_to_path_id(
+ &mut self, dcid_seq: u64, path_id: usize,
+ ) -> Result<()> {
+ let e = self.dcids.get_mut(dcid_seq).ok_or(Error::InvalidState)?;
+ e.path_id = Some(path_id);
+ Ok(())
+ }
+
+ /// Gets the minimum Source Connection ID sequence number whose removal has
+ /// not been requested yet.
+ #[inline]
+ pub fn lowest_usable_scid_seq(&self) -> Result<u64> {
+ self.scids
+ .iter()
+ .filter_map(|e| {
+ if e.seq >= self.retire_prior_to {
+ Some(e.seq)
+ } else {
+ None
+ }
+ })
+ .min()
+ .ok_or(Error::InvalidState)
+ }
+
+ /// Gets the lowest Destination Connection ID sequence number that is not
+ /// associated to a path.
+ #[inline]
+ pub fn lowest_available_dcid_seq(&self) -> Option<u64> {
+ self.dcids
+ .iter()
+ .filter_map(|e| {
+ if e.path_id.is_none() {
+ Some(e.seq)
+ } else {
+ None
+ }
+ })
+ .min()
+ }
+
+ /// Finds the sequence number of the Source Connection ID having the
+ /// provided value and the identifier of the path using it, if any.
+ #[inline]
+ pub fn find_scid_seq(
+ &self, scid: &ConnectionId,
+ ) -> Option<(u64, Option<usize>)> {
+ self.scids.iter().find_map(|e| {
+ if e.cid == *scid {
+ Some((e.seq, e.path_id))
+ } else {
+ None
+ }
+ })
+ }
+
+ /// Returns the number of Source Connection IDs that have not been
+ /// assigned to a path yet.
+ ///
+ /// Note that this function is only meaningful if the host uses non-zero
+ /// length Source Connection IDs.
+ #[inline]
+ pub fn available_scids(&self) -> usize {
+ self.scids.iter().filter(|e| e.path_id.is_none()).count()
+ }
+
+ /// Returns the number of Destination Connection IDs that have not been
+ /// assigned to a path yet.
+ ///
+ /// Note that this function returns 0 if the host uses zero length
+ /// Destination Connection IDs.
+ #[inline]
+ pub fn available_dcids(&self) -> usize {
+ if self.zero_length_dcid() {
+ return 0;
+ }
+ self.dcids.iter().filter(|e| e.path_id.is_none()).count()
+ }
+
+ /// Returns the oldest active source Connection ID of this connection.
+ #[inline]
+ pub fn oldest_scid(&self) -> &ConnectionIdEntry {
+ self.scids.get_oldest()
+ }
+
+ /// Returns the oldest known active destination Connection ID of this
+ /// connection.
+ ///
+ /// Note that due to e.g., reordering at reception side, the oldest known
+ /// active destination Connection ID is not necessarily the one having the
+ /// lowest sequence.
+ #[inline]
+ pub fn oldest_dcid(&self) -> &ConnectionIdEntry {
+ self.dcids.get_oldest()
+ }
+
+ /// Adds or remove the source Connection ID sequence number from the
+ /// source Connection ID set that need to be advertised to the peer through
+ /// NEW_CONNECTION_ID frames.
+ #[inline]
+ pub fn mark_advertise_new_scid_seq(
+ &mut self, scid_seq: u64, advertise: bool,
+ ) {
+ if advertise {
+ self.advertise_new_scid_seqs.push_back(scid_seq);
+ } else if let Some(index) = self
+ .advertise_new_scid_seqs
+ .iter()
+ .position(|s| *s == scid_seq)
+ {
+ self.advertise_new_scid_seqs.remove(index);
+ }
+ }
+
+ /// Adds or remove the destination Connection ID sequence number from the
+ /// retired destination Connection ID set that need to be advertised to the
+ /// peer through RETIRE_CONNECTION_ID frames.
+ #[inline]
+ pub fn mark_retire_dcid_seq(&mut self, dcid_seq: u64, retire: bool) {
+ if retire {
+ self.retire_dcid_seqs.push_back(dcid_seq);
+ } else if let Some(index) =
+ self.retire_dcid_seqs.iter().position(|s| *s == dcid_seq)
+ {
+ self.retire_dcid_seqs.remove(index);
+ }
+ }
+
+ /// Gets a source Connection ID's sequence number requiring advertising it
+ /// to the peer through NEW_CONNECTION_ID frame, if any.
+ ///
+ /// If `Some`, it always returns the same value until it has been removed
+ /// using `mark_advertise_new_scid_seq`.
+ #[inline]
+ pub fn next_advertise_new_scid_seq(&self) -> Option<u64> {
+ self.advertise_new_scid_seqs.front().copied()
+ }
+
+ /// Gets a destination Connection IDs's sequence number that need to send
+ /// RETIRE_CONNECTION_ID frames.
+ ///
+ /// If `Some`, it always returns the same value until it has been removed
+ /// using `mark_retire_dcid_seq`.
+ #[inline]
+ pub fn next_retire_dcid_seq(&self) -> Option<u64> {
+ self.retire_dcid_seqs.front().copied()
+ }
+
+ /// Returns true if there are new source Connection IDs to advertise.
+ #[inline]
+ pub fn has_new_scids(&self) -> bool {
+ !self.advertise_new_scid_seqs.is_empty()
+ }
+
+ /// Returns true if there are retired destination Connection IDs to\
+ /// advertise.
+ #[inline]
+ pub fn has_retire_dcids(&self) -> bool {
+ !self.retire_dcid_seqs.is_empty()
+ }
+
+ /// Returns whether zero-length source CIDs are used.
+ #[inline]
+ pub fn zero_length_scid(&self) -> bool {
+ self.zero_length_scid
+ }
+
+ /// Returns whether zero-length destination CIDs are used.
+ #[inline]
+ pub fn zero_length_dcid(&self) -> bool {
+ self.zero_length_dcid
+ }
+
+ /// Gets the NEW_CONNECTION_ID frame related to the source connection ID
+ /// with sequence `seq_num`.
+ pub fn get_new_connection_id_frame_for(
+ &self, seq_num: u64,
+ ) -> Result<frame::Frame> {
+ let e = self.scids.get(seq_num).ok_or(Error::InvalidState)?;
+ Ok(frame::Frame::NewConnectionId {
+ seq_num,
+ retire_prior_to: self.retire_prior_to,
+ conn_id: e.cid.to_vec(),
+ reset_token: e.reset_token.ok_or(Error::InvalidState)?.to_be_bytes(),
+ })
+ }
+
+ /// Returns the number of source Connection IDs that are active. This is
+ /// only meaningful if the host uses non-zero length Source Connection IDs.
+ #[inline]
+ pub fn active_source_cids(&self) -> usize {
+ self.scids.len()
+ }
+
+ pub fn pop_retired_scid(&mut self) -> Option<ConnectionId<'static>> {
+ self.retired_scids.pop_front()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::testing::create_cid_and_reset_token;
+
+ #[test]
+ fn ids_new_scids() {
+ let (scid, _) = create_cid_and_reset_token(16);
+ let (dcid, _) = create_cid_and_reset_token(16);
+
+ let mut ids = ConnectionIdentifiers::new(2, &scid, 0, None);
+ ids.set_source_conn_id_limit(3);
+ ids.set_initial_dcid(dcid, None, Some(0));
+
+ assert_eq!(ids.available_dcids(), 0);
+ assert_eq!(ids.available_scids(), 0);
+ assert_eq!(ids.has_new_scids(), false);
+ assert_eq!(ids.next_advertise_new_scid_seq(), None);
+
+ let (scid2, rt2) = create_cid_and_reset_token(16);
+
+ assert_eq!(ids.new_scid(scid2, Some(rt2), true, None, false), Ok(1));
+ assert_eq!(ids.available_dcids(), 0);
+ assert_eq!(ids.available_scids(), 1);
+ assert_eq!(ids.has_new_scids(), true);
+ assert_eq!(ids.next_advertise_new_scid_seq(), Some(1));
+
+ let (scid3, rt3) = create_cid_and_reset_token(16);
+
+ assert_eq!(ids.new_scid(scid3, Some(rt3), true, None, false), Ok(2));
+ assert_eq!(ids.available_dcids(), 0);
+ assert_eq!(ids.available_scids(), 2);
+ assert_eq!(ids.has_new_scids(), true);
+ assert_eq!(ids.next_advertise_new_scid_seq(), Some(1));
+
+ // If now we give another CID, it reports an error since it exceeds the
+ // limit of active CIDs.
+ let (scid4, rt4) = create_cid_and_reset_token(16);
+
+ assert_eq!(
+ ids.new_scid(scid4, Some(rt4), true, None, false),
+ Err(Error::IdLimit),
+ );
+ assert_eq!(ids.available_dcids(), 0);
+ assert_eq!(ids.available_scids(), 2);
+ assert_eq!(ids.has_new_scids(), true);
+ assert_eq!(ids.next_advertise_new_scid_seq(), Some(1));
+
+ // Assume we sent one of them.
+ ids.mark_advertise_new_scid_seq(1, false);
+ assert_eq!(ids.available_dcids(), 0);
+ assert_eq!(ids.available_scids(), 2);
+ assert_eq!(ids.has_new_scids(), true);
+ assert_eq!(ids.next_advertise_new_scid_seq(), Some(2));
+
+ // Send the other.
+ ids.mark_advertise_new_scid_seq(2, false);
+
+ assert_eq!(ids.available_dcids(), 0);
+ assert_eq!(ids.available_scids(), 2);
+ assert_eq!(ids.has_new_scids(), false);
+ assert_eq!(ids.next_advertise_new_scid_seq(), None);
+ }
+
+ #[test]
+ fn new_dcid_event() {
+ let (scid, _) = create_cid_and_reset_token(16);
+ let (dcid, _) = create_cid_and_reset_token(16);
+
+ let mut ids = ConnectionIdentifiers::new(2, &scid, 0, None);
+ ids.set_initial_dcid(dcid, None, Some(0));
+
+ assert_eq!(ids.available_dcids(), 0);
+ assert_eq!(ids.dcids.len(), 1);
+
+ let (dcid2, rt2) = create_cid_and_reset_token(16);
+
+ assert_eq!(
+ ids.new_dcid(dcid2.clone(), 1, rt2, 0),
+ Ok(Vec::<(u64, usize)>::new()),
+ );
+ assert_eq!(ids.available_dcids(), 1);
+ assert_eq!(ids.dcids.len(), 2);
+
+ // Now we assume that the client wants to advertise more source
+ // Connection IDs than the advertised limit. This is valid if it
+ // requests its peer to retire enough Connection IDs to fit within the
+ // limits.
+ let (dcid3, rt3) = create_cid_and_reset_token(16);
+ assert_eq!(ids.new_dcid(dcid3.clone(), 2, rt3, 1), Ok(vec![(0, 0)]));
+ // The CID module does not handle path replacing. Fake it now.
+ ids.link_dcid_to_path_id(1, 0).unwrap();
+ assert_eq!(ids.available_dcids(), 1);
+ assert_eq!(ids.dcids.len(), 2);
+ assert_eq!(ids.has_retire_dcids(), true);
+ assert_eq!(ids.next_retire_dcid_seq(), Some(0));
+
+ // Fake RETIRE_CONNECTION_ID sending.
+ ids.mark_retire_dcid_seq(0, false);
+ assert_eq!(ids.has_retire_dcids(), false);
+ assert_eq!(ids.next_retire_dcid_seq(), None);
+
+ // Now tries to experience CID retirement. If the server tries to remove
+ // non-existing DCIDs, it fails.
+ assert_eq!(ids.retire_dcid(0), Err(Error::InvalidState));
+ assert_eq!(ids.retire_dcid(3), Err(Error::InvalidState));
+ assert_eq!(ids.has_retire_dcids(), false);
+ assert_eq!(ids.dcids.len(), 2);
+
+ // Now it removes DCID with sequence 1.
+ assert_eq!(ids.retire_dcid(1), Ok(Some(0)));
+ // The CID module does not handle path replacing. Fake it now.
+ ids.link_dcid_to_path_id(2, 0).unwrap();
+ assert_eq!(ids.available_dcids(), 0);
+ assert_eq!(ids.has_retire_dcids(), true);
+ assert_eq!(ids.next_retire_dcid_seq(), Some(1));
+ assert_eq!(ids.dcids.len(), 1);
+
+ // Fake RETIRE_CONNECTION_ID sending.
+ ids.mark_retire_dcid_seq(1, false);
+ assert_eq!(ids.has_retire_dcids(), false);
+ assert_eq!(ids.next_retire_dcid_seq(), None);
+
+ // Trying to remove the last DCID triggers an error.
+ assert_eq!(ids.retire_dcid(2), Err(Error::OutOfIdentifiers));
+ assert_eq!(ids.available_dcids(), 0);
+ assert_eq!(ids.has_retire_dcids(), false);
+ assert_eq!(ids.dcids.len(), 1);
+ }
+
+ #[test]
+ fn retire_scids() {
+ let (scid, _) = create_cid_and_reset_token(16);
+ let (dcid, _) = create_cid_and_reset_token(16);
+
+ let mut ids = ConnectionIdentifiers::new(3, &scid, 0, None);
+ ids.set_initial_dcid(dcid, None, Some(0));
+ ids.set_source_conn_id_limit(3);
+
+ let (scid2, rt2) = create_cid_and_reset_token(16);
+ let (scid3, rt3) = create_cid_and_reset_token(16);
+
+ assert_eq!(
+ ids.new_scid(scid2.clone(), Some(rt2), true, None, false),
+ Ok(1),
+ );
+ assert_eq!(ids.scids.len(), 2);
+ assert_eq!(
+ ids.new_scid(scid3.clone(), Some(rt3), true, None, false),
+ Ok(2),
+ );
+ assert_eq!(ids.scids.len(), 3);
+
+ assert_eq!(ids.pop_retired_scid(), None);
+
+ assert_eq!(ids.retire_scid(0, &scid2), Ok(Some(0)));
+
+ assert_eq!(ids.pop_retired_scid(), Some(scid));
+ assert_eq!(ids.pop_retired_scid(), None);
+
+ assert_eq!(ids.retire_scid(1, &scid3), Ok(None));
+
+ assert_eq!(ids.pop_retired_scid(), Some(scid2));
+ assert_eq!(ids.pop_retired_scid(), None);
+ }
+}