tlsn_core/
transcript.rs

1//! Transcript types.
2//!
3//! All application data communicated over a TLS connection is referred to as a
4//! [`Transcript`]. A transcript is essentially just two vectors of bytes, each
5//! corresponding to a [`Direction`].
6//!
7//! TLS operates over a bidirectional byte stream, and thus there are no
8//! application layer semantics present in the transcript. For example, HTTPS is
9//! an application layer protocol that runs *over TLS* so there is no concept of
10//! "requests" or "responses" in the transcript itself. These semantics must be
11//! recovered by parsing the application data and relating it to the bytes
12//! in the transcript.
13//!
14//! ## Selective Disclosure
15//!
16//! Using a [`TranscriptProof`] a Prover can selectively disclose parts of a
17//! transcript to a Verifier in the form of a [`PartialTranscript`]. A Verifier
18//! always learns the length of the transcript, but sensitive data can be
19//! withheld.
20
21mod commit;
22pub mod encoding;
23pub mod hash;
24mod proof;
25mod tls;
26
27use std::{fmt, ops::Range};
28
29use rangeset::{Difference, IndexRanges, RangeSet, Union};
30use serde::{Deserialize, Serialize};
31
32use crate::connection::TranscriptLength;
33
34pub use commit::{
35    TranscriptCommitConfig, TranscriptCommitConfigBuilder, TranscriptCommitConfigBuilderError,
36    TranscriptCommitRequest, TranscriptCommitment, TranscriptCommitmentKind, TranscriptSecret,
37};
38pub use proof::{
39    TranscriptProof, TranscriptProofBuilder, TranscriptProofBuilderError, TranscriptProofError,
40};
41pub use tls::{ContentType, Record, TlsTranscript};
42
43/// A transcript contains the plaintext of all application data communicated
44/// between the Prover and the Server.
45#[derive(Clone, Serialize, Deserialize)]
46pub struct Transcript {
47    /// Data sent from the Prover to the Server.
48    sent: Vec<u8>,
49    /// Data received by the Prover from the Server.
50    received: Vec<u8>,
51}
52
53opaque_debug::implement!(Transcript);
54
55impl Transcript {
56    /// Creates a new transcript.
57    pub fn new(sent: impl Into<Vec<u8>>, received: impl Into<Vec<u8>>) -> Self {
58        Self {
59            sent: sent.into(),
60            received: received.into(),
61        }
62    }
63
64    /// Returns a reference to the sent data.
65    pub fn sent(&self) -> &[u8] {
66        &self.sent
67    }
68
69    /// Returns a reference to the received data.
70    pub fn received(&self) -> &[u8] {
71        &self.received
72    }
73
74    /// Returns the length of the sent and received data, respectively.
75    #[allow(clippy::len_without_is_empty)]
76    pub fn len(&self) -> (usize, usize) {
77        (self.sent.len(), self.received.len())
78    }
79
80    /// Returns the length of the transcript in the given direction.
81    pub(crate) fn len_of_direction(&self, direction: Direction) -> usize {
82        match direction {
83            Direction::Sent => self.sent.len(),
84            Direction::Received => self.received.len(),
85        }
86    }
87
88    /// Returns the transcript length.
89    pub fn length(&self) -> TranscriptLength {
90        TranscriptLength {
91            sent: self.sent.len() as u32,
92            received: self.received.len() as u32,
93        }
94    }
95
96    /// Returns the subsequence of the transcript with the provided index,
97    /// returning `None` if the index is out of bounds.
98    pub fn get(&self, direction: Direction, idx: &RangeSet<usize>) -> Option<Subsequence> {
99        let data = match direction {
100            Direction::Sent => &self.sent,
101            Direction::Received => &self.received,
102        };
103
104        if idx.end().unwrap_or(0) > data.len() {
105            return None;
106        }
107
108        Some(
109            Subsequence::new(idx.clone(), data.index_ranges(idx))
110                .expect("data is same length as index"),
111        )
112    }
113
114    /// Returns a partial transcript containing the provided indices.
115    ///
116    /// # Panics
117    ///
118    /// Panics if the indices are out of bounds.
119    ///
120    /// # Arguments
121    ///
122    /// * `sent_idx` - The indices of the sent data to include.
123    /// * `recv_idx` - The indices of the received data to include.
124    pub fn to_partial(
125        &self,
126        sent_idx: RangeSet<usize>,
127        recv_idx: RangeSet<usize>,
128    ) -> PartialTranscript {
129        let mut sent = vec![0; self.sent.len()];
130        let mut received = vec![0; self.received.len()];
131
132        for range in sent_idx.iter_ranges() {
133            sent[range.clone()].copy_from_slice(&self.sent[range]);
134        }
135
136        for range in recv_idx.iter_ranges() {
137            received[range.clone()].copy_from_slice(&self.received[range]);
138        }
139
140        PartialTranscript {
141            sent,
142            received,
143            sent_authed_idx: sent_idx,
144            received_authed_idx: recv_idx,
145        }
146    }
147}
148
149/// A partial transcript.
150///
151/// A partial transcript is a transcript which may not have all the data
152/// authenticated.
153#[derive(Debug, Clone, Serialize, Deserialize)]
154#[serde(try_from = "CompressedPartialTranscript")]
155#[serde(into = "CompressedPartialTranscript")]
156#[cfg_attr(test, derive(PartialEq))]
157pub struct PartialTranscript {
158    /// Data sent from the Prover to the Server.
159    sent: Vec<u8>,
160    /// Data received by the Prover from the Server.
161    received: Vec<u8>,
162    /// Index of `sent` which have been authenticated.
163    sent_authed_idx: RangeSet<usize>,
164    /// Index of `received` which have been authenticated.
165    received_authed_idx: RangeSet<usize>,
166}
167
168/// `PartialTranscript` in a compressed form.
169#[derive(Debug, Clone, Serialize, Deserialize)]
170#[serde(try_from = "validation::CompressedPartialTranscriptUnchecked")]
171pub struct CompressedPartialTranscript {
172    /// Sent data which has been authenticated.
173    sent_authed: Vec<u8>,
174    /// Received data which has been authenticated.
175    received_authed: Vec<u8>,
176    /// Index of `sent_authed`.
177    sent_idx: RangeSet<usize>,
178    /// Index of `received_authed`.
179    recv_idx: RangeSet<usize>,
180    /// Total bytelength of sent data in the original partial transcript.
181    sent_total: usize,
182    /// Total bytelength of received data in the original partial transcript.
183    recv_total: usize,
184}
185
186impl From<PartialTranscript> for CompressedPartialTranscript {
187    fn from(uncompressed: PartialTranscript) -> Self {
188        Self {
189            sent_authed: uncompressed
190                .sent
191                .index_ranges(&uncompressed.sent_authed_idx),
192            received_authed: uncompressed
193                .received
194                .index_ranges(&uncompressed.received_authed_idx),
195            sent_idx: uncompressed.sent_authed_idx,
196            recv_idx: uncompressed.received_authed_idx,
197            sent_total: uncompressed.sent.len(),
198            recv_total: uncompressed.received.len(),
199        }
200    }
201}
202
203impl From<CompressedPartialTranscript> for PartialTranscript {
204    fn from(compressed: CompressedPartialTranscript) -> Self {
205        let mut sent = vec![0; compressed.sent_total];
206        let mut received = vec![0; compressed.recv_total];
207
208        let mut offset = 0;
209
210        for range in compressed.sent_idx.iter_ranges() {
211            sent[range.clone()]
212                .copy_from_slice(&compressed.sent_authed[offset..offset + range.len()]);
213            offset += range.len();
214        }
215
216        let mut offset = 0;
217
218        for range in compressed.recv_idx.iter_ranges() {
219            received[range.clone()]
220                .copy_from_slice(&compressed.received_authed[offset..offset + range.len()]);
221            offset += range.len();
222        }
223
224        Self {
225            sent,
226            received,
227            sent_authed_idx: compressed.sent_idx,
228            received_authed_idx: compressed.recv_idx,
229        }
230    }
231}
232
233impl PartialTranscript {
234    /// Creates a new partial transcript initalized to all 0s.
235    ///
236    /// # Arguments
237    ///
238    /// * `sent_len` - The length of the sent data.
239    /// * `received_len` - The length of the received data.
240    pub fn new(sent_len: usize, received_len: usize) -> Self {
241        Self {
242            sent: vec![0; sent_len],
243            received: vec![0; received_len],
244            sent_authed_idx: RangeSet::default(),
245            received_authed_idx: RangeSet::default(),
246        }
247    }
248
249    /// Returns the length of the sent transcript.
250    pub fn len_sent(&self) -> usize {
251        self.sent.len()
252    }
253
254    /// Returns the length of the received transcript.
255    pub fn len_received(&self) -> usize {
256        self.received.len()
257    }
258
259    /// Returns whether the transcript is complete.
260    pub fn is_complete(&self) -> bool {
261        self.sent_authed_idx.len() == self.sent.len()
262            && self.received_authed_idx.len() == self.received.len()
263    }
264
265    /// Returns whether the index is in bounds of the transcript.
266    pub fn contains(&self, direction: Direction, idx: &RangeSet<usize>) -> bool {
267        match direction {
268            Direction::Sent => idx.end().unwrap_or(0) <= self.sent.len(),
269            Direction::Received => idx.end().unwrap_or(0) <= self.received.len(),
270        }
271    }
272
273    /// Returns a reference to the sent data.
274    ///
275    /// # Warning
276    ///
277    /// Not all of the data in the transcript may have been authenticated. See
278    /// [sent_authed](PartialTranscript::sent_authed) for a set of ranges which
279    /// have been.
280    pub fn sent_unsafe(&self) -> &[u8] {
281        &self.sent
282    }
283
284    /// Returns a reference to the received data.
285    ///
286    /// # Warning
287    ///
288    /// Not all of the data in the transcript may have been authenticated. See
289    /// [received_authed](PartialTranscript::received_authed) for a set of
290    /// ranges which have been.
291    pub fn received_unsafe(&self) -> &[u8] {
292        &self.received
293    }
294
295    /// Returns the index of sent data which have been authenticated.
296    pub fn sent_authed(&self) -> &RangeSet<usize> {
297        &self.sent_authed_idx
298    }
299
300    /// Returns the index of received data which have been authenticated.
301    pub fn received_authed(&self) -> &RangeSet<usize> {
302        &self.received_authed_idx
303    }
304
305    /// Returns the index of sent data which haven't been authenticated.
306    pub fn sent_unauthed(&self) -> RangeSet<usize> {
307        (0..self.sent.len()).difference(&self.sent_authed_idx)
308    }
309
310    /// Returns the index of received data which haven't been authenticated.
311    pub fn received_unauthed(&self) -> RangeSet<usize> {
312        (0..self.received.len()).difference(&self.received_authed_idx)
313    }
314
315    /// Returns an iterator over the authenticated data in the transcript.
316    pub fn iter(&self, direction: Direction) -> impl Iterator<Item = u8> + '_ {
317        let (data, authed) = match direction {
318            Direction::Sent => (&self.sent, &self.sent_authed_idx),
319            Direction::Received => (&self.received, &self.received_authed_idx),
320        };
321
322        authed.iter().map(|i| data[i])
323    }
324
325    /// Unions the authenticated data of this transcript with another.
326    ///
327    /// # Panics
328    ///
329    /// Panics if the other transcript is not the same length.
330    pub fn union_transcript(&mut self, other: &PartialTranscript) {
331        assert_eq!(
332            self.sent.len(),
333            other.sent.len(),
334            "sent data are not the same length"
335        );
336        assert_eq!(
337            self.received.len(),
338            other.received.len(),
339            "received data are not the same length"
340        );
341
342        for range in other
343            .sent_authed_idx
344            .difference(&self.sent_authed_idx)
345            .iter_ranges()
346        {
347            self.sent[range.clone()].copy_from_slice(&other.sent[range]);
348        }
349
350        for range in other
351            .received_authed_idx
352            .difference(&self.received_authed_idx)
353            .iter_ranges()
354        {
355            self.received[range.clone()].copy_from_slice(&other.received[range]);
356        }
357
358        self.sent_authed_idx = self.sent_authed_idx.union(&other.sent_authed_idx);
359        self.received_authed_idx = self.received_authed_idx.union(&other.received_authed_idx);
360    }
361
362    /// Unions an authenticated subsequence into this transcript.
363    ///
364    /// # Panics
365    ///
366    /// Panics if the subsequence is outside the bounds of the transcript.
367    pub fn union_subsequence(&mut self, direction: Direction, seq: &Subsequence) {
368        match direction {
369            Direction::Sent => {
370                seq.copy_to(&mut self.sent);
371                self.sent_authed_idx = self.sent_authed_idx.union(&seq.idx);
372            }
373            Direction::Received => {
374                seq.copy_to(&mut self.received);
375                self.received_authed_idx = self.received_authed_idx.union(&seq.idx);
376            }
377        }
378    }
379
380    /// Sets all bytes in the transcript which haven't been authenticated.
381    ///
382    /// # Arguments
383    ///
384    /// * `value` - The value to set the unauthenticated bytes to
385    pub fn set_unauthed(&mut self, value: u8) {
386        for range in self.sent_unauthed().iter_ranges() {
387            self.sent[range].fill(value);
388        }
389        for range in self.received_unauthed().iter_ranges() {
390            self.received[range].fill(value);
391        }
392    }
393
394    /// Sets all bytes in the transcript which haven't been authenticated within
395    /// the given range.
396    ///
397    /// # Arguments
398    ///
399    /// * `value` - The value to set the unauthenticated bytes to
400    /// * `range` - The range of bytes to set
401    pub fn set_unauthed_range(&mut self, value: u8, direction: Direction, range: Range<usize>) {
402        match direction {
403            Direction::Sent => {
404                for range in range.difference(&self.sent_authed_idx).iter_ranges() {
405                    self.sent[range].fill(value);
406                }
407            }
408            Direction::Received => {
409                for range in range.difference(&self.received_authed_idx).iter_ranges() {
410                    self.received[range].fill(value);
411                }
412            }
413        }
414    }
415}
416
417/// The direction of data communicated over a TLS connection.
418///
419/// This is used to differentiate between data sent from the Prover to the TLS
420/// peer, and data received by the Prover from the TLS peer (client or server).
421#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
422pub enum Direction {
423    /// Sent from the Prover to the TLS peer.
424    Sent = 0x00,
425    /// Received by the prover from the TLS peer.
426    Received = 0x01,
427}
428
429impl fmt::Display for Direction {
430    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
431        match self {
432            Direction::Sent => write!(f, "sent"),
433            Direction::Received => write!(f, "received"),
434        }
435    }
436}
437
438/// Transcript subsequence.
439#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
440#[serde(try_from = "validation::SubsequenceUnchecked")]
441pub struct Subsequence {
442    /// Index of the subsequence.
443    idx: RangeSet<usize>,
444    /// Data of the subsequence.
445    data: Vec<u8>,
446}
447
448impl Subsequence {
449    /// Creates a new subsequence.
450    pub fn new(idx: RangeSet<usize>, data: Vec<u8>) -> Result<Self, InvalidSubsequence> {
451        if idx.len() != data.len() {
452            return Err(InvalidSubsequence(
453                "index length does not match data length",
454            ));
455        }
456
457        Ok(Self { idx, data })
458    }
459
460    /// Returns the index of the subsequence.
461    pub fn index(&self) -> &RangeSet<usize> {
462        &self.idx
463    }
464
465    /// Returns the data of the subsequence.
466    pub fn data(&self) -> &[u8] {
467        &self.data
468    }
469
470    /// Returns the length of the subsequence.
471    #[allow(clippy::len_without_is_empty)]
472    pub fn len(&self) -> usize {
473        self.data.len()
474    }
475
476    /// Returns the inner parts of the subsequence.
477    pub fn into_parts(self) -> (RangeSet<usize>, Vec<u8>) {
478        (self.idx, self.data)
479    }
480
481    /// Copies the subsequence data into the given destination.
482    ///
483    /// # Panics
484    ///
485    /// Panics if the subsequence ranges are out of bounds.
486    pub(crate) fn copy_to(&self, dest: &mut [u8]) {
487        let mut offset = 0;
488        for range in self.idx.iter_ranges() {
489            dest[range.clone()].copy_from_slice(&self.data[offset..offset + range.len()]);
490            offset += range.len();
491        }
492    }
493}
494
495/// Invalid subsequence error.
496#[derive(Debug, thiserror::Error)]
497#[error("invalid subsequence: {0}")]
498pub struct InvalidSubsequence(&'static str);
499
500mod validation {
501    use super::*;
502
503    #[derive(Debug, Deserialize)]
504    pub(super) struct SubsequenceUnchecked {
505        idx: RangeSet<usize>,
506        data: Vec<u8>,
507    }
508
509    impl TryFrom<SubsequenceUnchecked> for Subsequence {
510        type Error = InvalidSubsequence;
511
512        fn try_from(unchecked: SubsequenceUnchecked) -> Result<Self, Self::Error> {
513            Self::new(unchecked.idx, unchecked.data)
514        }
515    }
516
517    /// Invalid compressed partial transcript error.
518    #[derive(Debug, thiserror::Error)]
519    #[error("invalid compressed partial transcript: {0}")]
520    pub struct InvalidCompressedPartialTranscript(&'static str);
521
522    #[derive(Debug, Deserialize)]
523    #[cfg_attr(test, derive(Serialize))]
524    pub(super) struct CompressedPartialTranscriptUnchecked {
525        sent_authed: Vec<u8>,
526        received_authed: Vec<u8>,
527        sent_idx: RangeSet<usize>,
528        recv_idx: RangeSet<usize>,
529        sent_total: usize,
530        recv_total: usize,
531    }
532
533    impl TryFrom<CompressedPartialTranscriptUnchecked> for CompressedPartialTranscript {
534        type Error = InvalidCompressedPartialTranscript;
535
536        fn try_from(unchecked: CompressedPartialTranscriptUnchecked) -> Result<Self, Self::Error> {
537            if unchecked.sent_authed.len() != unchecked.sent_idx.len()
538                || unchecked.received_authed.len() != unchecked.recv_idx.len()
539            {
540                return Err(InvalidCompressedPartialTranscript(
541                    "lengths of index and data don't match",
542                ));
543            }
544
545            if unchecked.sent_idx.end().unwrap_or(0) > unchecked.sent_total
546                || unchecked.recv_idx.end().unwrap_or(0) > unchecked.recv_total
547            {
548                return Err(InvalidCompressedPartialTranscript(
549                    "ranges are not in bounds of the data",
550                ));
551            }
552
553            Ok(Self {
554                received_authed: unchecked.received_authed,
555                recv_idx: unchecked.recv_idx,
556                recv_total: unchecked.recv_total,
557                sent_authed: unchecked.sent_authed,
558                sent_idx: unchecked.sent_idx,
559                sent_total: unchecked.sent_total,
560            })
561        }
562    }
563
564    #[cfg(test)]
565    mod tests {
566        use rstest::{fixture, rstest};
567
568        use super::*;
569
570        #[fixture]
571        fn partial_transcript() -> CompressedPartialTranscriptUnchecked {
572            CompressedPartialTranscriptUnchecked {
573                received_authed: vec![1, 2, 3, 11, 12, 13],
574                sent_authed: vec![4, 5, 6, 14, 15, 16],
575                recv_idx: RangeSet::from([1..4, 11..14]),
576                sent_idx: RangeSet::from([4..7, 14..17]),
577                sent_total: 20,
578                recv_total: 20,
579            }
580        }
581
582        #[rstest]
583        fn test_partial_transcript_valid(partial_transcript: CompressedPartialTranscriptUnchecked) {
584            let bytes = bincode::serialize(&partial_transcript).unwrap();
585            let transcript: Result<CompressedPartialTranscript, Box<bincode::ErrorKind>> =
586                bincode::deserialize(&bytes);
587            assert!(transcript.is_ok());
588        }
589
590        #[rstest]
591        // Expect to fail since the length of data and the length of the index do not
592        // match.
593        fn test_partial_transcript_invalid_lengths(
594            mut partial_transcript: CompressedPartialTranscriptUnchecked,
595        ) {
596            // Add an extra byte to the data.
597            let mut old = partial_transcript.sent_authed;
598            old.extend([1]);
599            partial_transcript.sent_authed = old;
600
601            let bytes = bincode::serialize(&partial_transcript).unwrap();
602            let transcript: Result<CompressedPartialTranscript, Box<bincode::ErrorKind>> =
603                bincode::deserialize(&bytes);
604            assert!(transcript.is_err());
605        }
606
607        #[rstest]
608        // Expect to fail since the index is out of bounds.
609        fn test_partial_transcript_invalid_ranges(
610            mut partial_transcript: CompressedPartialTranscriptUnchecked,
611        ) {
612            // Change the total to be less than the last range's end bound.
613            let end = partial_transcript
614                .sent_idx
615                .iter_ranges()
616                .next_back()
617                .unwrap()
618                .end;
619
620            partial_transcript.sent_total = end - 1;
621
622            let bytes = bincode::serialize(&partial_transcript).unwrap();
623            let transcript: Result<CompressedPartialTranscript, Box<bincode::ErrorKind>> =
624                bincode::deserialize(&bytes);
625            assert!(transcript.is_err());
626        }
627    }
628}
629
630#[cfg(test)]
631mod tests {
632    use rstest::{fixture, rstest};
633
634    use super::*;
635
636    #[fixture]
637    fn transcript() -> Transcript {
638        Transcript::new(
639            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
640            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
641        )
642    }
643
644    #[fixture]
645    fn partial_transcript() -> PartialTranscript {
646        transcript().to_partial(RangeSet::from([1..4, 6..9]), RangeSet::from([2..5, 7..10]))
647    }
648
649    #[rstest]
650    fn test_transcript_get_subsequence(transcript: Transcript) {
651        let subseq = transcript
652            .get(Direction::Received, &RangeSet::from([0..4, 7..10]))
653            .unwrap();
654        assert_eq!(subseq.data, vec![0, 1, 2, 3, 7, 8, 9]);
655
656        let subseq = transcript
657            .get(Direction::Sent, &RangeSet::from([0..4, 9..12]))
658            .unwrap();
659        assert_eq!(subseq.data, vec![0, 1, 2, 3, 9, 10, 11]);
660
661        let subseq = transcript.get(Direction::Received, &RangeSet::from([0..4, 7..10, 11..13]));
662        assert_eq!(subseq, None);
663
664        let subseq = transcript.get(Direction::Sent, &RangeSet::from([0..4, 7..10, 11..13]));
665        assert_eq!(subseq, None);
666    }
667
668    #[rstest]
669    fn test_partial_transcript_serialization_ok(partial_transcript: PartialTranscript) {
670        let bytes = bincode::serialize(&partial_transcript).unwrap();
671        let deserialized_transcript: PartialTranscript = bincode::deserialize(&bytes).unwrap();
672        assert_eq!(partial_transcript, deserialized_transcript);
673    }
674
675    #[rstest]
676    fn test_transcript_to_partial_success(transcript: Transcript) {
677        let partial = transcript.to_partial(RangeSet::from(0..2), RangeSet::from(3..7));
678        assert_eq!(partial.sent_unsafe(), [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
679        assert_eq!(
680            partial.received_unsafe(),
681            [0, 0, 0, 3, 4, 5, 6, 0, 0, 0, 0, 0]
682        );
683    }
684
685    #[rstest]
686    #[should_panic]
687    fn test_transcript_to_partial_failure(transcript: Transcript) {
688        let _ = transcript.to_partial(RangeSet::from(0..14), RangeSet::from(3..7));
689    }
690
691    #[rstest]
692    fn test_partial_transcript_contains(transcript: Transcript) {
693        let partial = transcript.to_partial(RangeSet::from(0..2), RangeSet::from(3..7));
694        assert!(partial.contains(Direction::Sent, &RangeSet::from([0..5, 7..10])));
695        assert!(!partial.contains(Direction::Received, &RangeSet::from([4..6, 7..13])))
696    }
697
698    #[rstest]
699    fn test_partial_transcript_unauthed(transcript: Transcript) {
700        let partial = transcript.to_partial(RangeSet::from(0..2), RangeSet::from(3..7));
701        assert_eq!(partial.sent_unauthed(), RangeSet::from(2..12));
702        assert_eq!(partial.received_unauthed(), RangeSet::from([0..3, 7..12]));
703    }
704
705    #[rstest]
706    fn test_partial_transcript_union_success(transcript: Transcript) {
707        // Non overlapping ranges.
708        let mut simple_partial = transcript.to_partial(RangeSet::from(0..2), RangeSet::from(3..7));
709
710        let other_simple_partial =
711            transcript.to_partial(RangeSet::from(3..5), RangeSet::from(1..2));
712
713        simple_partial.union_transcript(&other_simple_partial);
714
715        assert_eq!(
716            simple_partial.sent_unsafe(),
717            [0, 1, 0, 3, 4, 0, 0, 0, 0, 0, 0, 0]
718        );
719        assert_eq!(
720            simple_partial.received_unsafe(),
721            [0, 1, 0, 3, 4, 5, 6, 0, 0, 0, 0, 0]
722        );
723        assert_eq!(simple_partial.sent_authed(), &RangeSet::from([0..2, 3..5]));
724        assert_eq!(
725            simple_partial.received_authed(),
726            &RangeSet::from([1..2, 3..7])
727        );
728
729        // Overwrite with another partial transcript.
730
731        let another_simple_partial =
732            transcript.to_partial(RangeSet::from(1..4), RangeSet::from(6..9));
733
734        simple_partial.union_transcript(&another_simple_partial);
735
736        assert_eq!(
737            simple_partial.sent_unsafe(),
738            [0, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0]
739        );
740        assert_eq!(
741            simple_partial.received_unsafe(),
742            [0, 1, 0, 3, 4, 5, 6, 7, 8, 0, 0, 0]
743        );
744        assert_eq!(simple_partial.sent_authed(), &RangeSet::from(0..5));
745        assert_eq!(
746            simple_partial.received_authed(),
747            &RangeSet::from([1..2, 3..9])
748        );
749
750        // Overlapping ranges.
751        let mut overlap_partial = transcript.to_partial(RangeSet::from(4..6), RangeSet::from(3..7));
752
753        let other_overlap_partial =
754            transcript.to_partial(RangeSet::from(3..5), RangeSet::from(5..9));
755
756        overlap_partial.union_transcript(&other_overlap_partial);
757
758        assert_eq!(
759            overlap_partial.sent_unsafe(),
760            [0, 0, 0, 3, 4, 5, 0, 0, 0, 0, 0, 0]
761        );
762        assert_eq!(
763            overlap_partial.received_unsafe(),
764            [0, 0, 0, 3, 4, 5, 6, 7, 8, 0, 0, 0]
765        );
766        assert_eq!(overlap_partial.sent_authed(), &RangeSet::from([3..5, 4..6]));
767        assert_eq!(
768            overlap_partial.received_authed(),
769            &RangeSet::from([3..7, 5..9])
770        );
771
772        // Equal ranges.
773        let mut equal_partial = transcript.to_partial(RangeSet::from(4..6), RangeSet::from(3..7));
774
775        let other_equal_partial = transcript.to_partial(RangeSet::from(4..6), RangeSet::from(3..7));
776
777        equal_partial.union_transcript(&other_equal_partial);
778
779        assert_eq!(
780            equal_partial.sent_unsafe(),
781            [0, 0, 0, 0, 4, 5, 0, 0, 0, 0, 0, 0]
782        );
783        assert_eq!(
784            equal_partial.received_unsafe(),
785            [0, 0, 0, 3, 4, 5, 6, 0, 0, 0, 0, 0]
786        );
787        assert_eq!(equal_partial.sent_authed(), &RangeSet::from(4..6));
788        assert_eq!(equal_partial.received_authed(), &RangeSet::from(3..7));
789
790        // Subset ranges.
791        let mut subset_partial =
792            transcript.to_partial(RangeSet::from(4..10), RangeSet::from(3..11));
793
794        let other_subset_partial =
795            transcript.to_partial(RangeSet::from(6..9), RangeSet::from(5..6));
796
797        subset_partial.union_transcript(&other_subset_partial);
798
799        assert_eq!(
800            subset_partial.sent_unsafe(),
801            [0, 0, 0, 0, 4, 5, 6, 7, 8, 9, 0, 0]
802        );
803        assert_eq!(
804            subset_partial.received_unsafe(),
805            [0, 0, 0, 3, 4, 5, 6, 7, 8, 9, 10, 0]
806        );
807        assert_eq!(subset_partial.sent_authed(), &RangeSet::from(4..10));
808        assert_eq!(subset_partial.received_authed(), &RangeSet::from(3..11));
809    }
810
811    #[rstest]
812    #[should_panic]
813    fn test_partial_transcript_union_failure(transcript: Transcript) {
814        let mut partial = transcript.to_partial(RangeSet::from(4..10), RangeSet::from(3..11));
815
816        let other_transcript = Transcript::new(
817            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
818            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
819        );
820
821        let other_partial = other_transcript.to_partial(RangeSet::from(6..9), RangeSet::from(5..6));
822
823        partial.union_transcript(&other_partial);
824    }
825
826    #[rstest]
827    fn test_partial_transcript_union_subseq_success(transcript: Transcript) {
828        let mut partial = transcript.to_partial(RangeSet::from(4..10), RangeSet::from(3..11));
829        let sent_seq =
830            Subsequence::new(RangeSet::from([0..3, 5..7]), [0, 1, 2, 5, 6].into()).unwrap();
831        let recv_seq =
832            Subsequence::new(RangeSet::from([0..4, 5..7]), [0, 1, 2, 3, 5, 6].into()).unwrap();
833
834        partial.union_subsequence(Direction::Sent, &sent_seq);
835        partial.union_subsequence(Direction::Received, &recv_seq);
836
837        assert_eq!(partial.sent_unsafe(), [0, 1, 2, 0, 4, 5, 6, 7, 8, 9, 0, 0]);
838        assert_eq!(
839            partial.received_unsafe(),
840            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0]
841        );
842        assert_eq!(partial.sent_authed(), &RangeSet::from([0..3, 4..10]));
843        assert_eq!(partial.received_authed(), &RangeSet::from(0..11));
844
845        // Overwrite with another subseq.
846        let other_sent_seq = Subsequence::new(RangeSet::from(0..3), [3, 2, 1].into()).unwrap();
847
848        partial.union_subsequence(Direction::Sent, &other_sent_seq);
849        assert_eq!(partial.sent_unsafe(), [3, 2, 1, 0, 4, 5, 6, 7, 8, 9, 0, 0]);
850        assert_eq!(partial.sent_authed(), &RangeSet::from([0..3, 4..10]));
851    }
852
853    #[rstest]
854    #[should_panic]
855    fn test_partial_transcript_union_subseq_failure(transcript: Transcript) {
856        let mut partial = transcript.to_partial(RangeSet::from(4..10), RangeSet::from(3..11));
857
858        let sent_seq =
859            Subsequence::new(RangeSet::from([0..3, 13..15]), [0, 1, 2, 5, 6].into()).unwrap();
860
861        partial.union_subsequence(Direction::Sent, &sent_seq);
862    }
863
864    #[rstest]
865    fn test_partial_transcript_set_unauthed_range(transcript: Transcript) {
866        let mut partial = transcript.to_partial(RangeSet::from(4..10), RangeSet::from(3..7));
867
868        partial.set_unauthed_range(7, Direction::Sent, 2..5);
869        partial.set_unauthed_range(5, Direction::Sent, 0..2);
870        partial.set_unauthed_range(3, Direction::Received, 4..6);
871        partial.set_unauthed_range(1, Direction::Received, 3..7);
872
873        assert_eq!(partial.sent_unsafe(), [5, 5, 7, 7, 4, 5, 6, 7, 8, 9, 0, 0]);
874        assert_eq!(
875            partial.received_unsafe(),
876            [0, 0, 0, 3, 4, 5, 6, 0, 0, 0, 0, 0]
877        );
878    }
879
880    #[rstest]
881    #[should_panic]
882    fn test_subsequence_new_invalid_len() {
883        let _ = Subsequence::new(RangeSet::from([0..3, 5..8]), [0, 1, 2, 5, 6].into()).unwrap();
884    }
885
886    #[rstest]
887    #[should_panic]
888    fn test_subsequence_copy_to_invalid_len() {
889        let seq = Subsequence::new(RangeSet::from([0..3, 5..7]), [0, 1, 2, 5, 6].into()).unwrap();
890
891        let mut data: [u8; 3] = [0, 1, 2];
892        seq.copy_to(&mut data);
893    }
894}