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