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