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