kcode_k1_access_profile_codec/
lib.rs1pub use kcode_k1_access_profile_values::{
7 AuthorizationProfile, GroupId, ModelId, ProfileOwner, ProfileViewer, TxId, UserId,
8};
9
10pub fn encode_profile(profile: &AuthorizationProfile) -> Result<Vec<u8>, String> {
16 let owner_count = u32::try_from(profile.owners().len())
17 .map_err(|_| "owner count exceeds canonical encoding".to_owned())?;
18 let viewer_count = u32::try_from(profile.viewers().len())
19 .map_err(|_| "viewer count exceeds canonical encoding".to_owned())?;
20 let mut bytes = Vec::new();
21 bytes.push(1);
22 bytes.extend_from_slice(&owner_count.to_be_bytes());
23 for owner in profile.owners() {
24 match owner {
25 ProfileOwner::RequestUser => bytes.push(0),
26 ProfileOwner::User(user) => {
27 bytes.push(1);
28 bytes.extend_from_slice(user.as_tx_id().as_bytes());
29 }
30 ProfileOwner::Group(group) => {
31 bytes.push(2);
32 bytes.extend_from_slice(group.txid().as_bytes());
33 }
34 }
35 }
36 bytes.extend_from_slice(&viewer_count.to_be_bytes());
37 for viewer in profile.viewers() {
38 match viewer {
39 ProfileViewer::RequestUser => bytes.push(0),
40 ProfileViewer::RequestModel => bytes.push(1),
41 ProfileViewer::User(user) => {
42 bytes.push(2);
43 bytes.extend_from_slice(user.as_tx_id().as_bytes());
44 }
45 ProfileViewer::Group(group) => {
46 bytes.push(3);
47 bytes.extend_from_slice(group.txid().as_bytes());
48 }
49 ProfileViewer::Model(model) => {
50 bytes.push(4);
51 bytes.extend_from_slice(model.as_bytes());
52 }
53 }
54 }
55 Ok(bytes)
56}
57
58pub fn decode_profile(bytes: &[u8]) -> Result<AuthorizationProfile, String> {
63 let mut reader = Reader::new(bytes);
64 if reader.u8()? != 1 {
65 return Err("unknown profile encoding version".to_owned());
66 }
67 let owner_count = usize::try_from(reader.u32()?)
68 .map_err(|_| "owner count does not fit this platform".to_owned())?;
69 let mut owners = Vec::new();
70 for _ in 0..owner_count {
71 owners.push(match reader.u8()? {
72 0 => ProfileOwner::RequestUser,
73 1 => ProfileOwner::User(UserId::from_tx_id(TxId::from_bytes(reader.take()?))),
74 2 => ProfileOwner::Group(GroupId::new(TxId::from_bytes(reader.take()?))),
75 _ => return Err("unknown profile owner tag".to_owned()),
76 });
77 }
78 let viewer_count = usize::try_from(reader.u32()?)
79 .map_err(|_| "viewer count does not fit this platform".to_owned())?;
80 let mut viewers = Vec::new();
81 for _ in 0..viewer_count {
82 viewers.push(match reader.u8()? {
83 0 => ProfileViewer::RequestUser,
84 1 => ProfileViewer::RequestModel,
85 2 => ProfileViewer::User(UserId::from_tx_id(TxId::from_bytes(reader.take()?))),
86 3 => ProfileViewer::Group(GroupId::new(TxId::from_bytes(reader.take()?))),
87 4 => ProfileViewer::Model(ModelId::from_bytes(reader.take()?)),
88 _ => return Err("unknown profile viewer tag".to_owned()),
89 });
90 }
91 if !reader.finished() {
92 return Err("trailing bytes in profile encoding".to_owned());
93 }
94 let profile = AuthorizationProfile::new(owners, viewers)?;
95 if encode_profile(&profile)?.as_slice() != bytes {
96 return Err("profile encoding is not canonical".to_owned());
97 }
98 Ok(profile)
99}
100
101struct Reader<'a> {
102 bytes: &'a [u8],
103 offset: usize,
104}
105
106impl<'a> Reader<'a> {
107 const fn new(bytes: &'a [u8]) -> Self {
108 Self { bytes, offset: 0 }
109 }
110
111 fn u8(&mut self) -> Result<u8, String> {
112 Ok(self.take::<1>()?[0])
113 }
114
115 fn u32(&mut self) -> Result<u32, String> {
116 Ok(u32::from_be_bytes(self.take()?))
117 }
118
119 fn take<const N: usize>(&mut self) -> Result<[u8; N], String> {
120 let end = self
121 .offset
122 .checked_add(N)
123 .ok_or_else(|| "truncated profile encoding".to_owned())?;
124 let source = self
125 .bytes
126 .get(self.offset..end)
127 .ok_or_else(|| "truncated profile encoding".to_owned())?;
128 let mut output = [0; N];
129 output.copy_from_slice(source);
130 self.offset = end;
131 Ok(output)
132 }
133
134 fn finished(&self) -> bool {
135 self.offset == self.bytes.len()
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use super::{ProfileOwner as O, ProfileViewer as V};
143
144 fn canonical_case() -> (AuthorizationProfile, Vec<u8>) {
145 let tx = |byte| TxId::from_bytes([byte; 12]);
146 let profile = AuthorizationProfile::new(
147 vec![
148 O::Group(GroupId::new(tx(3))),
149 O::RequestUser,
150 O::User(UserId::from_tx_id(tx(2))),
151 ],
152 vec![
153 V::Model(ModelId::from_bytes([5; 32])),
154 V::Group(GroupId::new(tx(4))),
155 V::RequestUser,
156 V::User(UserId::from_tx_id(tx(3))),
157 V::RequestModel,
158 ],
159 )
160 .expect("profile");
161 let bytes = [
162 &[1, 0, 0, 0, 3, 0, 1][..],
163 &[2; 12],
164 &[2],
165 &[3; 12],
166 &[0, 0, 0, 5, 0, 1, 2],
167 &[3; 12],
168 &[3],
169 &[4; 12],
170 &[4],
171 &[5; 32],
172 ]
173 .concat();
174 (profile, bytes)
175 }
176
177 #[test]
178 fn emits_the_exact_canonical_vector() {
179 let (profile, expected) = canonical_case();
180 assert_eq!(encode_profile(&profile).expect("encoding"), expected);
181 }
182
183 #[test]
184 fn canonical_vector_round_trips() {
185 let (profile, bytes) = canonical_case();
186 assert_eq!(decode_profile(&bytes).expect("decoding"), profile);
187 assert_eq!(
188 encode_profile(&decode_profile(&bytes).unwrap()).unwrap(),
189 bytes
190 );
191 }
192
193 #[test]
194 fn rejects_truncated_input() {
195 assert_eq!(
196 decode_profile(&[]).unwrap_err(),
197 "truncated profile encoding"
198 );
199 assert_eq!(
200 decode_profile(&[1, 0, 0, 0, 1, 1]).unwrap_err(),
201 "truncated profile encoding"
202 );
203 }
204
205 #[test]
206 fn rejects_unknown_version_and_tags() {
207 for (input, expected) in [
208 (vec![2], "unknown profile encoding version"),
209 (vec![1, 0, 0, 0, 1, 9], "unknown profile owner tag"),
210 (
211 vec![1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 9],
212 "unknown profile viewer tag",
213 ),
214 ] {
215 assert_eq!(decode_profile(&input).unwrap_err(), expected);
216 }
217 }
218
219 #[test]
220 fn rejects_empty_owner_set() {
221 assert_eq!(
222 decode_profile(&[1, 0, 0, 0, 0, 0, 0, 0, 0]).unwrap_err(),
223 "authorization profile requires at least one owner"
224 );
225 }
226
227 #[test]
228 fn rejects_noncanonical_order_and_duplicates() {
229 let out_of_order = [&[1, 0, 0, 0, 2, 2][..], &[1; 12], &[0, 0, 0, 0, 0]].concat();
230 let duplicate = vec![1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0];
231 for input in [out_of_order, duplicate] {
232 assert_eq!(
233 decode_profile(&input).unwrap_err(),
234 "profile encoding is not canonical"
235 );
236 }
237 }
238
239 #[test]
240 fn rejects_trailing_bytes() {
241 assert_eq!(
242 decode_profile(&[1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]).unwrap_err(),
243 "trailing bytes in profile encoding"
244 );
245 }
246}