/// The number of invocations remaining on a write cipher before we try /// to update keys. This has to be much smaller than the number returned /// by `CryptoDxState::limit` or updates will happen too often. As we don't /// need to ask permission to update, this can be quite small. pubconst UPDATE_WRITE_KEYS_AT: packet::Number = 100;
// This is a testing kludge that allows for overwriting the number of // invocations of the next cipher to operate. With this, it is possible // to test what happens when the number of invocations reaches 0, or // when it hits `UPDATE_WRITE_KEYS_AT` and an automatic update should occur. // This is a little crude, but it saves a lot of plumbing. #[cfg(test)]
thread_local!(pubstatic OVERWRITE_INVOCATIONS: RefCell<Option<packet::Number>> = RefCell::default());
// Always enable 0-RTT on the client, but the server needs // more configuration passed to server_enable_0rtt.
c.enable_0rtt()?;
}
agent.set_alpn(&protocols)?;
agent.disable_end_of_early_data()?; let extension = match version {
Version::Version2 | Version::Version1 => 0x39, #[cfg(feature = "draft-29")]
Version::Draft29 => 0xffa5,
};
agent.extension_handler(extension, tphandler)?;
Ok(Self {
version,
protocols,
tls: agent,
streams: CryptoStreams::default(),
states: CryptoStates::default(),
})
}
/// Get the name of the server. (Only works for the client currently). pubfn server_name(&self) -> Option<&str> { iflet Agent::Client(c) = &self.tls {
Some(c.server_name())
} else {
None
}
}
/// Get the set of enabled protocols. pubfn protocols(&self) -> &[String] {
&self.protocols
}
/// Enable 0-RTT and return `true` if it is enabled successfully. pubfn enable_0rtt(&mutself, version: Version, role: Role) -> Res<bool> { let info = self.tls.preinfo()?; // `info.early_data()` returns false for a server, // so use `early_data_cipher()` to tell if 0-RTT is enabled. let Some(cipher) = info.early_data_cipher() else { return Ok(false);
}; let (dir, secret) = match role {
Role::Client => (
CryptoDxDirection::Write, self.tls.write_secret(Epoch::ZeroRtt),
),
Role::Server => (
CryptoDxDirection::Read, self.tls.read_secret(Epoch::ZeroRtt),
),
}; let secret = secret.ok_or(Error::Internal)?; self.states.set_0rtt_keys(version, dir, &secret, cipher)?;
Ok(true)
}
/// Lock in a compatible upgrade. pubfn confirm_version(&mutself, confirmed: Version) -> Res<()> { self.states.confirm_version(self.version, confirmed)?; self.version = confirmed;
Ok(())
}
/// Returns true if new handshake keys were installed. pubfn install_keys(&mutself, role: Role) -> Res<bool> { ifself.tls.state().is_final() {
Ok(false)
} else { let installed_hs = self.install_handshake_keys()?; if role == Role::Server { self.maybe_install_application_write_key(self.version)?;
}
Ok(installed_hs)
}
}
fn install_handshake_keys(&mutself) -> Res<bool> {
qtrace!("[{self}] Attempt to install handshake keys"); let Some(write_secret) = self.tls.write_secret(Epoch::Handshake) else { // No keys is fine. return Ok(false);
}; let read_secret = self
.tls
.read_secret(Epoch::Handshake)
.ok_or(Error::Internal)?; let cipher = matchself.tls.info() {
None => self.tls.preinfo()?.cipher_suite(),
Some(info) => Some(info.cipher_suite()),
}
.ok_or(Error::Internal)?; self.states
.set_handshake_keys(self.version, &write_secret, &read_secret, cipher)?;
qdebug!("[{self}] Handshake keys installed");
Ok(true)
}
pubfn install_application_keys(&mutself, version: Version, expire_0rtt: Instant) -> Res<()> { self.maybe_install_application_write_key(version)?; // The write key might have been installed earlier, but it should // always be installed now.
debug_assert!(self.states.app_write.is_some()); let read_secret = self
.tls
.read_secret(Epoch::ApplicationData)
.ok_or(Error::Internal)?; self.states
.set_application_read_key(version, &read_secret, expire_0rtt)?;
qdebug!("[{self}] application read keys installed");
Ok(())
}
/// Buffer crypto records for sending. pubfn buffer_records(&mutself, records: RecordList) -> Res<()> { for r in records { if r.ct != TLS_CT_HANDSHAKE { return Err(Error::ProtocolViolation);
}
qtrace!("[{self}] Adding CRYPTO data {r:?}"); self.streams.send(r.epoch.into(), &r.data)?;
}
Ok(())
}
/// Mark any outstanding frames in the indicated space as "lost" so /// that they can be sent again. pubfn resend_unacked(&mutself, space: PacketNumberSpace) { self.streams.resend_unacked(space);
}
/// Discard state for a packet number space and return true /// if something was discarded. pubfn discard(&mutself, space: PacketNumberSpace) -> bool { self.streams.discard(space); self.states.discard(space)
}
impl From<CryptoDxDirection> for Mode { fn from(dir: CryptoDxDirection) -> Self { match dir {
CryptoDxDirection::Read => Self::Decrypt,
CryptoDxDirection::Write => Self::Encrypt,
}
}
}
#[derive(Debug)] pubstruct CryptoDxState { /// The QUIC version.
version: Version, /// Whether packets protected with this state will be read or written.
direction: CryptoDxDirection, /// The epoch of this crypto state. This initially tracks TLS epochs /// via DTLS: 0 = initial, 1 = 0-RTT, 2 = handshake, 3 = application. /// But we don't need to keep that, and QUIC isn't limited in how /// many times keys can be updated, so we don't use `u16` for this.
epoch: usize,
aead: Aead,
hpkey: hp::Key, /// This tracks the range of packet numbers that have been seen. This allows /// for verifying that packet numbers before a key update are strictly lower /// than packet numbers after a key update.
used_pn: Range<packet::Number>, /// This is the minimum packet number that is allowed.
min_pn: packet::Number, /// The total number of operations that are remaining before the keys /// become exhausted and can't be used any more.
invocations: packet::Number, /// The basis of the invocation limits in `invocations`.
largest_packet_len: usize,
}
/// Determine whether we should initiate a key update. pubfn should_update(&self) -> bool { // There is no point in updating read keys as the limit is global.
debug_assert_eq!(self.direction, CryptoDxDirection::Write); self.invocations <= UPDATE_WRITE_KEYS_AT
}
pubfn next(&self, next_secret: &SymKey, cipher: Cipher) -> Res<Self> { let pn = self.next_pn(); // We count invocations of each write key just for that key, but all // attempts to invocations to read count toward a single limit. // This doesn't count use of Handshake keys. let invocations = ifself.direction == CryptoDxDirection::Read { self.invocations
} else { Self::limit(CryptoDxDirection::Write, cipher)
};
Ok(Self {
version: self.version,
direction: self.direction,
epoch: self.epoch + 1,
aead: Aead::new(
TLS_VERSION_1_3,
cipher,
next_secret, self.version.label_prefix(),
Mode::from(self.direction),
)?,
hpkey: self.hpkey.try_clone()?,
used_pn: pn..pn,
min_pn: pn,
invocations,
largest_packet_len: INITIAL_LARGEST_PACKET_LEN,
})
}
#[must_use] pubconstfn version(&self) -> Version { self.version
}
/// This is a continuation of a previous, so adjust the range accordingly. /// Fail if the two ranges overlap. Do nothing if the directions don't match. pubfn continuation(&mutself, prev: &Self) -> Res<()> {
debug_assert_eq!(self.direction, prev.direction); let next = prev.next_pn(); self.min_pn = next; ifself.used_pn.is_empty() { self.used_pn = next..next;
Ok(())
} elseif prev.used_pn.end > self.used_pn.start {
qdebug!( "[{self}] Found packet with too new packet number {} > {}, compared to {prev}", self.used_pn.start,
prev.used_pn.end,
);
Err(Error::PacketNumberOverlap)
} else { self.used_pn.start = next;
Ok(())
}
}
/// Mark a packet number as used. If this is too low, reject it. /// Note that this won't catch a value that is too high if packets protected with /// old keys are received after a key update. That needs to be caught elsewhere. pubfn used(&mutself, pn: packet::Number) -> Res<()> { if pn < self.min_pn {
qdebug!( "[{self}] Found packet with too old packet number: {pn} < {}", self.min_pn
); return Err(Error::PacketNumberOverlap);
} ifself.used_pn.start == self.used_pn.end { self.used_pn.start = pn;
} self.used_pn.end = max(pn + 1, self.used_pn.end);
Ok(())
}
#[must_use] pubfn needs_update(&self) -> bool { // Only initiate a key update if we have processed exactly one packet // and we are in an epoch greater than 3. self.used_pn.start + 1 == self.used_pn.end
&& self.epoch > usize::from(Epoch::ApplicationData)
}
// The numbers in `Self::limit` assume a maximum packet size of `LIMIT`. // Adjust them as we encounter larger packets. let body_len = data.len() - hdr.len() - self.aead.expansion();
debug_assert!(body_len <= u16::MAX.into()); if body_len > self.largest_packet_len { let new_bits = usize::leading_zeros(self.largest_packet_len - 1)
- usize::leading_zeros(body_len - 1); self.invocations >>= new_bits; self.largest_packet_len = body_len;
} self.invoked()?;
let (prev, data) = data.split_at_mut(hdr.end); // `prev` may have already-encrypted packets this one is being coalesced with. // Use only the actual current header for AAD. let len = self.aead.encrypt_in_place(pn, &prev[hdr], data)?;
#[cfg(not(feature = "disable-encryption"))] #[cfg(test)] fn test_default_with_direction(direction: CryptoDxDirection) -> Self { // This matches the value in packet.rs const CLIENT_CID: &[u8] = &[0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08]; Self::new_initial(Version::default(), direction, "server in", CLIENT_CID, 0).unwrap()
}
/// Get the amount of extra padding packets protected with this profile need. /// This is the difference between the size of the header protection sample /// and the AEAD expansion. pubfn extra_padding(&self) -> usize {
hp::Key::SAMPLE_SIZE.saturating_sub(self.expansion())
}
}
/// `CryptoDxAppData` wraps the state necessary for one direction of application data keys. /// This includes the secret needed to generate the next set of keys. #[derive(Debug)] pubstruct CryptoDxAppData {
dx: CryptoDxState,
cipher: Cipher, // Not the secret used to create `self.dx`, but the one needed for the next iteration.
next_secret: SymKey,
}
/// All of the keying material needed for a connection. /// /// Note that the methods on this struct take a version but those are only ever /// used for Initial keys; a version has been selected at the time we need to /// get other keys, so those have fixed versions. #[derive(Debug, Default)] pubstruct CryptoStates {
initials: EnumMap<Version, Option<CryptoState>>,
handshake: Option<CryptoState>,
zero_rtt: Option<CryptoDxState>, // One direction only!
cipher: Cipher,
app_write: Option<CryptoDxAppData>,
app_read: Option<CryptoDxAppData>,
app_read_next: Option<CryptoDxAppData>, // If this is set, then we have noticed a genuine update. // Once this time passes, we should switch in new keys.
read_update_time: Option<Instant>,
}
/// When decrypting Initial packets, there are potentially multiple active versions. /// The `used_pn` range tracks what has been received on the version that was used. /// But if the version changes, the version we select might have a value of 0, /// rather than the actual value, which can cause packet number recovery to fail. /// To avoid that, have the indicated `version` continue from the previous version. /// This only needs to be run once, so run it when getting header protection. fn maybe_continue_initial_rx(&mutself, version: Version) { // Only do this if this version hasn't been used... ifself.initials[version]
.as_ref()
.is_none_or(|dx| dx.rx.next_pn() != 0)
{ return;
} // ... and some other version has been. // This assumes that there is just one other version in use, // as the spec requires. let Some(other) = self
.initials
.iter()
.find_map(|(k, v)| v.as_ref().is_some_and(|z| z.rx.next_pn() > 0).then_some(k)) else { return;
};
debug_assert_ne!(version, other);
// This uses the take-modify-restore pattern to avoid // having the borrow checker complain. // It *ignores* errors from the `continuation()` // so that the restore step isn't skipped. // // This doesn't need to be full anti-replay. // Each version has separate keys, so nonce reuse is OK. // After this, we might reject packets if the peer // does reuse nonces, but they aren't allowed to do that. // // Note: these `if let Some(...)` conditions are always true. iflet Some(mut next) = self.initials[version].take() { iflet Some(prev) = &self.initials[other] {
_ = next.rx.continuation(&prev.rx);
} self.initials[version] = Some(next);
}
}
pubfn rx<'a>(
&'a mut self,
version: Version,
epoch: Epoch,
key_phase: bool,
) -> Option<&'a mut CryptoDxState> { let rx = |x: Option<&'a mut CryptoState>| x.map(|dx| &mut dx.rx); match epoch {
Epoch::Initial => rx(self.initials[version].as_mut()),
Epoch::ZeroRtt => self
.zero_rtt
.as_mut()
.filter(|z| z.direction == CryptoDxDirection::Read),
Epoch::Handshake => rx(self.handshake.as_mut()),
Epoch::ApplicationData => { let f = |a: Option<&'a mut CryptoDxAppData>| {
a.filter(|ar| ar.dx.key_phase() == key_phase)
}; // XOR to reduce the leakage about which key is chosen.
f(self.app_read.as_mut())
.xor(f(self.app_read_next.as_mut()))
.map(|ar| &mut ar.dx)
}
}
}
/// Whether keys for processing packets in the indicated space are pending. /// This allows the caller to determine whether to save a packet for later /// when keys are not available. /// NOTE: 0-RTT keys are not considered here. The expectation is that a /// server will have to save 0-RTT packets in a different place. Though it /// is possible to attribute 0-RTT packets to an existing connection if there /// is a multi-packet Initial, that is an unusual circumstance, so we /// don't do caching for that in those places that call this function. pubfn rx_pending(&self, space: Epoch) -> bool { match space {
Epoch::Initial | Epoch::ZeroRtt => false,
Epoch::Handshake => self.handshake.is_none() && !self.initials_is_empty(),
Epoch::ApplicationData => self.app_read.is_none(),
}
}
/// Create the initial crypto state. /// Note that the version here can change and that's OK. pubfn init<'v, V>(
&mutself,
versions: V,
role: Role,
dcid: &[u8],
randomize_first_pn: bool,
) -> Res<()> where
V: IntoIterator<Item = &'v Version>,
{ const CLIENT_INITIAL_LABEL: &str = "client in"; const SERVER_INITIAL_LABEL: &str = "server in";
let (write, read) = match role {
Role::Client => (CLIENT_INITIAL_LABEL, SERVER_INITIAL_LABEL),
Role::Server => (SERVER_INITIAL_LABEL, CLIENT_INITIAL_LABEL),
};
let min_pn = if randomize_first_pn { let r = random::<2>(); // A random starting packet number that is mostly less than 64, // but can go as high as 1024, in three parts: // - A value from 0..31. // - A value from 0..1024 in steps of 32, but only one time in eight. // - An extra 1, just to ensure that the result is always non-zero.
packet::Number::from(r[0] & 0x1f)
+ (packet::Number::from(r[1].saturating_sub(224)) << 5)
+ 1
} else { 0
};
for v in versions {
qdebug!( "[{self}] Creating initial cipher state v={v:?}, role={role:?} dcid={}",
hex(dcid)
);
/// At a server, we can be more targeted in initializing. /// Initialize on demand: either to decrypt Initial packets that we receive /// or after a version has been selected. /// This is maybe slightly inefficient in the first case, because we might /// not need the send keys if the packet is subsequently discarded, but /// the overall effort is small enough to write off. pubfn init_server(
&mutself,
version: Version,
dcid: &[u8],
randomize_first_pn: bool,
) -> Res<()> { ifself.initials[version].is_none() { self.init(&[version], Role::Server, dcid, randomize_first_pn)?;
}
Ok(())
}
pubfn confirm_version(&mutself, orig: Version, confirmed: Version) -> Res<()> { if orig != confirmed { // This part where the old data is removed and then re-added is to // appease the borrow checker. // Note that on the server, we might not have initials for |orig| if it // was configured for |orig| and only |confirmed| Initial packets arrived. iflet Some(prev) = self.initials[orig].take() { let next = self.initials[confirmed]
.as_mut()
.ok_or(Error::VersionNegotiation)?;
next.rx.continuation(&prev.rx)?;
next.tx.continuation(&prev.tx)?; self.initials[orig] = Some(prev);
}
}
Ok(())
}
/// Update the write keys. pubfn initiate_key_update(&mutself, largest_acknowledged: Option<packet::Number>) -> Res<()> { // Only update if we are able to. We can only do this if we have // received an acknowledgement for a packet in the current phase. // Also, skip this if we are waiting for read keys on the existing // key update to be rolled over. let write = &self.app_write.as_ref().ok_or(Error::Internal)?.dx; if write.can_update(largest_acknowledged) && self.read_update_time.is_none() { // This call additionally checks that we don't advance to the next // epoch while a key update is in progress. ifself.maybe_update_write()? {
Ok(())
} else {
qdebug!("[{self}] Write keys already updated");
Err(Error::KeyUpdateBlocked)
}
} else {
qdebug!("[{self}] Waiting for ACK or blocked on read key timer");
Err(Error::KeyUpdateBlocked)
}
}
/// Try to update, and return true if it happened. fn maybe_update_write(&mutself) -> Res<bool> { // Update write keys. But only do so if the write keys are not already // ahead of the read keys. If we initiated the key update, the write keys // will already be ahead.
debug_assert!(self.read_update_time.is_none()); let write = &self.app_write.as_ref().ok_or(Error::Internal)?; let read = &self.app_read.as_ref().ok_or(Error::Internal)?; if write.epoch() == read.epoch() {
qdebug!("[{self}] Update write keys to epoch={}", write.epoch() + 1); self.app_write = Some(write.next()?);
Ok(true)
} else {
Ok(false)
}
}
/// Check whether write keys are close to running out of invocations. /// If that is close, update them if possible. Failing to update at /// this stage is cause for a fatal error. pubfn auto_update(&mutself) -> Res<()> { iflet Some(app_write) = self.app_write.as_ref()
&& app_write.dx.should_update()
{
qinfo!("[{self}] Initiating automatic key update"); if !self.maybe_update_write()? { return Err(Error::KeysExhausted);
}
}
Ok(())
}
/// Prepare to update read keys. This doesn't happen immediately as /// we want to ensure that we can continue to receive any delayed /// packets that use the old keys. So we just set a timer. pubfn key_update_received(&mutself, expiration: Instant) -> Res<()> {
qtrace!("[{self}] Key update received"); // If we received a key update, then we assume that the peer has // acknowledged a packet we sent in this epoch. It's OK to do that // because they aren't allowed to update without first having received // something from us. If the ACK isn't in the packet that triggered this // key update, it must be in some other packet they have sent.
_ = self.maybe_update_write()?;
// We shouldn't have 0-RTT keys at this point, but if we do, dump them.
debug_assert_eq!(self.read_update_time.is_some(), self.has_0rtt_read()); ifself.has_0rtt_read() { self.zero_rtt = None;
} self.read_update_time = Some(expiration);
Ok(())
}
/// Check if time has passed for updating key update parameters. /// If it has, then swap keys over and allow more key updates to be initiated. /// This is also used to discard 0-RTT read keys at the server in the same way. pubfn check_key_update(&mutself, now: Instant) -> Res<()> { iflet Some(expiry) = self.read_update_time { // If enough time has passed, then install new keys and clear the timer. if now >= expiry { ifself.has_0rtt_read() {
qtrace!("[{self}] Discarding 0-RTT keys"); self.zero_rtt = None;
} else {
qtrace!("[{self}] Rotating read keys");
mem::swap(&mutself.app_read, &mutself.app_read_next); self.app_read_next =
Some(self.app_read.as_ref().ok_or(Error::Internal)?.next()?);
} self.read_update_time = None;
}
}
Ok(())
}
/// Get the current/highest epoch. This returns (write, read) epochs. #[cfg(test)] pubfn get_epochs(&self) -> (Option<usize>, Option<usize>) { let to_epoch = |app: &Option<CryptoDxAppData>| app.as_ref().map(|a| a.dx.epoch);
(to_epoch(&self.app_write), to_epoch(&>self.app_read))
}
/// While we are awaiting the completion of a key update, we might receive /// valid packets that are protected with old keys. We need to ensure that /// these don't carry packet numbers higher than those in packets protected /// with the newer keys. To ensure that, this is called after every decryption. pubfn check_pn_overlap(&mutself) -> Res<()> { // We only need to do the check while we are waiting for read keys to be updated. ifself.read_update_time.is_some() {
qtrace!("[{self}] Checking for PN overlap"); let next_dx = &mutself.app_read_next.as_mut().ok_or(Error::Internal)?.dx;
next_dx.continuation(&self.app_read.as_ref().ok_or(Error::Internal)?.dx)?;
}
Ok(())
}
/// Make some state for removing protection in tests. #[cfg(not(feature = "disable-encryption"))] #[cfg(test)] pub(crate) fn test_default() -> Self { let read = |epoch| { letmut dx = CryptoDxState::test_default_read();
dx.epoch = epoch;
dx
}; let app_read = |epoch| CryptoDxAppData {
dx: read(epoch),
cipher: TLS_AES_128_GCM_SHA256,
next_secret: hkdf::import_key(TLS_VERSION_1_3, &[0xaa; 32]).unwrap(),
}; let initials = EnumMap::from_array([
None,
Some(CryptoState {
tx: CryptoDxState::test_default_write(),
rx: read(0),
}),
None,
]); Self {
initials,
handshake: None,
zero_rtt: None,
cipher: TLS_AES_128_GCM_SHA256, // This isn't used, but the epoch is read to check for a key update.
app_write: Some(app_read(3)),
app_read: Some(app_read(3)),
app_read_next: Some(app_read(4)),
read_update_time: None,
}
}
pubfn lost(&mutself, token: &CryptoRecoveryToken) { // See BZ 1624800, ignore lost packets in spaces we've dropped keys iflet Some(cs) = self.get_mut(token.space) {
cs.tx.mark_as_lost(token.offset, token.length);
}
}
/// Resend any Initial or Handshake CRYPTO frames that might be outstanding. /// This can help speed up handshake times. pubfn resend_unacked(&mutself, space: PacketNumberSpace) { if space != PacketNumberSpace::ApplicationData
&& let Some(cs) = self.get_mut(space)
{
cs.tx.unmark_sent();
}
}
// Don't bother if there isn't room for the header and some data. if builder.remaining() < header_len + 1 { return None;
} // Calculate length of data based on the minimum of: // - available data // - remaining space, less the header, which counts only one byte for the length at // first to avoid underestimating length
let length = min(data.len(), builder.remaining() - header_len);
header_len +=
Encoder::varint_len(u64::try_from(length).expect("usize fits in u64")) - 1;
let length = min(data.len(), builder.remaining() - header_len);
#[expect(clippy::type_complexity, reason = "Yeah, a bit complex but still OK.")] const fn limit_chunks<'a>(
left: (u64, &'a [u8]),
right: (u64, &'a [u8]),
limit: usize,
) -> ((u64, &'a [u8]), (u64, &'a [u8])) {
let (left_offset, mut left) = left;
let (mut right_offset, mut right) = right; if left.len() + right.len() <= limit { // Nothing to do. Both chunks will fit into one packet, meaning the SNI isn't spread // over multiple packets. But at least it's in two unordered CRYPTO frames.
} elseif left.len() <= limit { // `left` is short enough to fit into this packet. So send from the *end* // of `right`, so that the second half of the SNI is in another packet.
let right_len = right.len() + left.len() - limit;
right_offset += right_len as u64;
(_, right) = right.split_at(right_len);
} elseif right.len() <= limit { // `right` is short enough to fit into this packet. So only send a part of `left`. // The SNI begins at the end of `left`, so send the beginnig of it in this packet.
(left, _) = left.split_at(limit - right.len());
} else { // Both chunks are too long to fit into one packet. Just send a part of each.
(left, _) = left.split_at(limit / 2);
(right, _) = right.split_at(limit / 2);
}
((left_offset, left), (right_offset, right))
}
let Some(cs) = self.get_mut(space) else { return;
}; while let Some((offset, data)) = cs.tx.next_bytes() { #[cfg(feature = "build-fuzzing-corpus")] if offset == 0 {
neqo_common::write_item_to_fuzzing_corpus("find_sni", data);
}
let written = if sni_slicing && offset == 0 { if let Some(sni) = find_sni(data) { // Cut the crypto data in two at the midpoint of the SNI and swap the chunks.
let mid = sni.start + (sni.end - sni.start) / 2;
let (left, right) = data.split_at(mid);
// Truncate the chunks so we can fit them into roughly evenly-filled packets.
let packets_needed = data.len().div_ceil(builder.limit());
let limit = data.len() / packets_needed;
let ((left_offset, left), (right_offset, right)) =
limit_chunks((offset, left), (offset + mid as u64, right), limit);
(
write_chunk(right_offset, right, builder),
write_chunk(left_offset, left, builder),
)
} else { // No SNI found, write the entire data.
(write_chunk(offset, data, builder), None)
}
} else { // SNI slicing disabled or data not at offset 0, write the entire data.
(write_chunk(offset, data, builder), None)
};
match written {
(None, None) => break,
(None, Some((offset, len))) | (Some((offset, len)), None) => {
mark_as_sent(cs, space, tokens, offset, len, stats);
}
(Some((offset1, len1)), Some((offset2, len2))) => {
mark_as_sent(cs, space, tokens, offset1, len1, stats);
mark_as_sent(cs, space, tokens, offset2, len2, stats); // We only end up in this arm if we successfully sliced above. In that case, // don't try and fit more crypto data into this packet. break;
}
}
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.