Skip to main content

dryoc/dryocstream/
tag.rs

1use crate::constants::{
2    CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL,
3    CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE,
4    CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PUSH,
5    CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY,
6};
7use crate::error::{Error, ErrorContext, ValueConstraint};
8
9/// Secret stream message tag.
10///
11/// Each message pushed to a [`DryocStream`](super::DryocStream) carries one
12/// tag, which is encrypted and authenticated with the message. The variants
13/// are exactly libsodium's four `crypto_secretstream_xchacha20poly1305_TAG_*`
14/// values, and [`Tag::bits`] returns the byte stored in the stream. Convert a
15/// tag byte with [`Tag::try_from`], which rejects every other byte.
16///
17/// `Tag` is `#[non_exhaustive]` so that a tag value libsodium may add to the
18/// secretstream format later can become a new variant without a breaking
19/// change; a `match` on `Tag` outside this crate therefore needs a wildcard
20/// arm.
21#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
22#[non_exhaustive]
23#[repr(u8)]
24pub enum Tag {
25    /// A normal message in a stream (`TAG_MESSAGE`, `0`).
26    #[default]
27    Message = 0,
28    /// Marks the end of a series of messages in a stream, but not the end of
29    /// the stream (`TAG_PUSH`, `1`).
30    Push    = 1,
31    /// Derives a new key for the stream after this message (`TAG_REKEY`, `2`).
32    Rekey   = 2,
33    /// Marks the end of the stream, and rekeys after this message
34    /// (`TAG_FINAL`, `3`).
35    Final   = 3,
36}
37
38const _: () = {
39    assert!(Tag::Message as u8 == CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE);
40    assert!(Tag::Push as u8 == CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PUSH);
41    assert!(Tag::Rekey as u8 == CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY);
42    assert!(Tag::Final as u8 == CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL);
43};
44
45impl Tag {
46    /// Returns the tag byte, as stored in the stream and used by the Classic
47    /// [`crypto_secretstream_xchacha20poly1305`](crate::classic::crypto_secretstream_xchacha20poly1305)
48    /// functions.
49    #[inline]
50    #[must_use]
51    pub const fn bits(self) -> u8 {
52        self as u8
53    }
54}
55
56impl TryFrom<u8> for Tag {
57    type Error = Error;
58
59    /// Converts a tag byte into a [`Tag`].
60    ///
61    /// # Errors
62    ///
63    /// Returns [`Error::InvalidValue`] with [`ErrorContext::Tag`] for any byte
64    /// other than the four libsodium tag values.
65    fn try_from(bits: u8) -> Result<Self, Self::Error> {
66        match bits {
67            CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE => Ok(Self::Message),
68            CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PUSH => Ok(Self::Push),
69            CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY => Ok(Self::Rekey),
70            CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL => Ok(Self::Final),
71            _ => Err(Error::InvalidValue {
72                context: ErrorContext::Tag,
73                actual: u64::from(bits),
74                constraint: ValueConstraint::AllowedBits {
75                    mask: u64::from(Self::Final.bits()),
76                },
77            }),
78        }
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::Tag;
85
86    #[test]
87    fn tag_bytes_are_libsodiums_values() {
88        let cases = [
89            (Tag::Message, 0u8),
90            (Tag::Push, 1),
91            (Tag::Rekey, 2),
92            (Tag::Final, 3),
93        ];
94        for (tag, bits) in cases {
95            assert_eq!(tag.bits(), bits);
96            assert_eq!(Tag::try_from(bits).expect("known tag byte"), tag);
97        }
98    }
99
100    #[test]
101    fn tag_from_u8_rejects_every_unknown_byte() {
102        for bits in 4..=u8::MAX {
103            let error = Tag::try_from(bits).expect_err("unknown tag byte must be rejected");
104            assert!(
105                matches!(
106                    error,
107                    crate::Error::InvalidValue {
108                        context: crate::ErrorContext::Tag,
109                        actual,
110                        constraint: crate::ValueConstraint::AllowedBits { mask: 0x3 },
111                    } if actual == u64::from(bits)
112                ),
113                "tag byte {bits:#04x}: {error:?}"
114            );
115        }
116    }
117}