nix_compat/nar/wire/tag.rs
1/// A type implementing Tag represents a static hash set of byte strings,
2/// with a very simple perfect hash function: every element has a unique
3/// discriminant at a common byte offset. The values of the type represent
4/// the members by this single discriminant byte; they are indices into the
5/// hash set.
6pub trait Tag: Sized {
7 /// Discriminant offset
8 const OFF: usize;
9 /// Minimum variant length
10 const MIN: usize;
11
12 /// Minimal suitably sized buffer for reading the wire representation
13 ///
14 /// HACK: This is a workaround for const generics limitations.
15 type Buf: AsMut<[u8]> + Send;
16
17 /// Make an instance of [Self::Buf]
18 fn make_buf() -> Self::Buf;
19
20 /// Convert a discriminant into the corresponding variant
21 fn from_u8(x: u8) -> Option<Self>;
22
23 /// Convert a variant back into the wire representation
24 fn as_bytes(&self) -> &'static [u8];
25}
26
27/// Generate an enum implementing [Tag], enforcing at compile time that
28/// the discriminant values are distinct.
29macro_rules! make {
30 (
31 $(
32 $(#[doc = $doc:expr])*
33 $vis:vis enum $Enum:ident[$off:expr] {
34 $(
35 $(#[doc = $var_doc:expr])*
36 $Var:ident = $TOK:ident,
37 )+
38 }
39 )*
40 ) => {
41 $(
42 $(#[doc = $doc])*
43 #[derive(Debug, PartialEq, Eq)]
44 #[repr(u8)]
45 $vis enum $Enum {
46 $(
47 $(#[doc = $var_doc])*
48 $Var = $TOK[$Enum::OFF]
49 ),+
50 }
51
52 impl Tag for $Enum {
53 /// Discriminant offset
54 const OFF: usize = $off;
55 /// Minimum variant length
56 const MIN: usize = tag::min_of(&[$($TOK.len()),+]);
57
58 /// Minimal suitably sized buffer for reading the wire representation
59 type Buf = [u8; tag::buf_of(&[$($TOK.len()),+])];
60
61 /// Make an instance of [Self::Buf]
62 #[inline(always)]
63 fn make_buf() -> Self::Buf {
64 [0u8; tag::buf_of(&[$($TOK.len()),+])]
65 }
66
67 /// Convert a discriminant into the corresponding variant
68 #[inline(always)]
69 fn from_u8(x: u8) -> Option<Self> {
70 #[allow(non_upper_case_globals)]
71 mod __variant {
72 $(
73 pub const $Var: u8 = super::$Enum::$Var as u8;
74 )+
75 }
76
77 match x {
78 $(__variant::$Var => Some(Self::$Var),)+
79 _ => None
80 }
81 }
82
83 /// Convert a variant back into the wire representation
84 #[inline(always)]
85 fn as_bytes(&self) -> &'static [u8] {
86 match self {
87 $(Self::$Var => &$TOK,)+
88 }
89 }
90 }
91 )*
92 };
93}
94
95// The following functions are written somewhat unusually,
96// since they're const functions that cannot use iterators.
97
98/// Maximum element of a slice
99const fn max_of(mut xs: &[usize]) -> usize {
100 let mut y = usize::MIN;
101 while let &[x, ref tail @ ..] = xs {
102 y = if x > y { x } else { y };
103 xs = tail;
104 }
105 y
106}
107
108/// Minimum element of a slice
109pub const fn min_of(mut xs: &[usize]) -> usize {
110 let mut y = usize::MAX;
111 while let &[x, ref tail @ ..] = xs {
112 y = if x < y { x } else { y };
113 xs = tail;
114 }
115 y
116}
117
118/// Minimum buffer size to contain either of `0..Tag::MIN` and `Tag::MIN..`
119/// at a particular time, for all possible tag wire representations, given
120/// the sizes of all wire representations.
121///
122/// # Example
123///
124/// ```plain
125/// OFF = 16
126/// MIN = 24
127/// MAX = 64
128///
129/// BUF = max(MIN, MAX-MIN)
130/// = max(24, 64-24)
131/// = max(24, 40)
132/// = 40
133/// ```
134pub const fn buf_of(xs: &[usize]) -> usize {
135 max_of(&[min_of(xs), max_of(xs) - min_of(xs)])
136}
137
138pub(crate) use make;
139
140#[cfg(test)]
141mod test {
142 use super::super::tag::{self, Tag};
143
144 const TOK_A: [u8; 3] = [0xed, 0xef, 0x1c];
145 const TOK_B: [u8; 3] = [0xed, 0xf0, 0x1c];
146
147 const OFFSET: usize = 1;
148
149 make! {
150 enum Token[OFFSET] {
151 A = TOK_A,
152 B = TOK_B,
153 }
154 }
155
156 #[test]
157 fn example() {
158 assert_eq!(Token::from_u8(0xed), None);
159
160 let tag = Token::from_u8(0xef).unwrap();
161 assert_eq!(tag.as_bytes(), &TOK_A[..]);
162
163 let tag = Token::from_u8(0xf0).unwrap();
164 assert_eq!(tag.as_bytes(), &TOK_B[..]);
165 }
166}