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