1use kcode_k1_access_types::{
2 AccessId, Authorizations, GroupId, ModelId, OwnerSubject, SubsystemId, Target, TxId, UserId,
3 ViewerSubject,
4};
5#[derive(Clone, Debug, Eq, PartialEq)]
6pub enum OwnerWitness {
7 User,
8 Group(GroupId),
9}
10#[derive(Clone, Debug, Eq, PartialEq)]
11pub enum AccessAction {
12 Create {
13 target: Target,
14 authorizations: Authorizations,
15 },
16 Replace {
17 access_id: AccessId,
18 actor: UserId,
19 groups_revision: Option<TxId>,
20 witness: OwnerWitness,
21 authorizations: Authorizations,
22 },
23}
24fn error(message: &str) -> String {
25 message.to_owned()
26}
27fn reserve<T>(values: &mut Vec<T>, count: usize) -> Result<(), String> {
28 values
29 .try_reserve_exact(count)
30 .map_err(|_| error("allocation unavailable"))
31}
32fn wire_len(value: usize) -> Result<u64, String> {
33 u64::try_from(value).map_err(|_| error("length overflow"))
34}
35fn add(total: &mut usize, value: usize) -> Result<(), String> {
36 *total = total
37 .checked_add(value)
38 .ok_or_else(|| error("length overflow"))?;
39 Ok(())
40}
41fn authorization_len(authorizations: &Authorizations) -> Result<usize, String> {
42 wire_len(authorizations.owners().len())?;
43 wire_len(authorizations.viewers().len())?;
44 let owner_bytes = authorizations
45 .owners()
46 .len()
47 .checked_mul(13)
48 .ok_or_else(|| error("length overflow"))?;
49 let mut total = 16;
50 add(&mut total, owner_bytes)?;
51 for viewer in authorizations.viewers() {
52 let width = match viewer {
53 ViewerSubject::User(_) | ViewerSubject::Group(_) => 13,
54 ViewerSubject::Model(_) => 33,
55 };
56 add(&mut total, width)?;
57 }
58 Ok(total)
59}
60fn encoded_len(action: &AccessAction) -> Result<usize, String> {
61 let (mut total, authorizations) = match action {
62 AccessAction::Create {
63 target,
64 authorizations,
65 } => {
66 wire_len(target.object_id().len())?;
67 let mut total = 46;
68 add(&mut total, target.object_id().len())?;
69 (total, authorizations)
70 }
71 AccessAction::Replace {
72 groups_revision,
73 witness,
74 authorizations,
75 ..
76 } => {
77 let mut total = 44;
78 if groups_revision.is_some() {
79 add(&mut total, 12)?;
80 }
81 if matches!(witness, OwnerWitness::Group(_)) {
82 add(&mut total, 12)?;
83 }
84 (total, authorizations)
85 }
86 };
87 add(&mut total, authorization_len(authorizations)?)?;
88 Ok(total)
89}
90fn put_len(output: &mut Vec<u8>, value: usize) -> Result<(), String> {
91 output.extend_from_slice(&wire_len(value)?.to_le_bytes());
92 Ok(())
93}
94fn put_authorizations(output: &mut Vec<u8>, authorizations: &Authorizations) -> Result<(), String> {
95 put_len(output, authorizations.owners().len())?;
96 for owner in authorizations.owners() {
97 match owner {
98 OwnerSubject::User(id) => {
99 output.push(1);
100 output.extend_from_slice(id.as_tx_id().as_bytes());
101 }
102 OwnerSubject::Group(id) => {
103 output.push(2);
104 output.extend_from_slice(id.txid().as_bytes());
105 }
106 }
107 }
108 put_len(output, authorizations.viewers().len())?;
109 for viewer in authorizations.viewers() {
110 match viewer {
111 ViewerSubject::User(id) => {
112 output.push(1);
113 output.extend_from_slice(id.as_tx_id().as_bytes());
114 }
115 ViewerSubject::Group(id) => {
116 output.push(2);
117 output.extend_from_slice(id.txid().as_bytes());
118 }
119 ViewerSubject::Model(id) => {
120 output.push(3);
121 output.extend_from_slice(id.as_bytes());
122 }
123 }
124 }
125 Ok(())
126}
127pub fn encode(operation_id: [u8; 16], action: &AccessAction) -> Result<Vec<u8>, String> {
128 let length = encoded_len(action)?;
129 let mut output = Vec::new();
130 reserve(&mut output, length)?;
131 output.push(1);
132 output.push(match action {
133 AccessAction::Create { .. } => 1,
134 AccessAction::Replace { .. } => 2,
135 });
136 output.extend_from_slice(&operation_id);
137 match action {
138 AccessAction::Create {
139 target,
140 authorizations,
141 } => {
142 output.extend_from_slice(target.subsystem().as_bytes());
143 put_len(&mut output, target.object_id().len())?;
144 output.extend_from_slice(target.object_id());
145 put_authorizations(&mut output, authorizations)?;
146 }
147 AccessAction::Replace {
148 access_id,
149 actor,
150 groups_revision,
151 witness,
152 authorizations,
153 } => {
154 output.extend_from_slice(access_id.txid().as_bytes());
155 output.extend_from_slice(actor.as_tx_id().as_bytes());
156 match groups_revision {
157 None => output.push(0),
158 Some(revision) => {
159 output.push(1);
160 output.extend_from_slice(revision.as_bytes());
161 }
162 }
163 match witness {
164 OwnerWitness::User => output.push(1),
165 OwnerWitness::Group(group) => {
166 output.push(2);
167 output.extend_from_slice(group.txid().as_bytes());
168 }
169 }
170 put_authorizations(&mut output, authorizations)?;
171 }
172 }
173 debug_assert_eq!(output.len(), length);
174 Ok(output)
175}
176struct Reader<'a> {
177 bytes: &'a [u8],
178 position: usize,
179}
180impl<'a> Reader<'a> {
181 fn new(bytes: &'a [u8]) -> Self {
182 Self { bytes, position: 0 }
183 }
184 fn remaining(&self) -> usize {
185 self.bytes.len() - self.position
186 }
187 fn take(&mut self, length: usize) -> Result<&'a [u8], String> {
188 let end = self
189 .position
190 .checked_add(length)
191 .ok_or_else(|| error("length overflow"))?;
192 let value = self
193 .bytes
194 .get(self.position..end)
195 .ok_or_else(|| error("truncated payload"))?;
196 self.position = end;
197 Ok(value)
198 }
199 fn byte(&mut self) -> Result<u8, String> {
200 Ok(self.take(1)?[0])
201 }
202 fn array<const N: usize>(&mut self) -> Result<[u8; N], String> {
203 let mut value = [0; N];
204 value.copy_from_slice(self.take(N)?);
205 Ok(value)
206 }
207 fn length(&mut self) -> Result<usize, String> {
208 usize::try_from(u64::from_le_bytes(self.array()?)).map_err(|_| error("length overflow"))
209 }
210 fn count(&mut self, minimum_entry: usize) -> Result<usize, String> {
211 let count = self.length()?;
212 let minimum = count
213 .checked_mul(minimum_entry)
214 .ok_or_else(|| error("count overflow"))?;
215 if minimum > self.remaining() {
216 return Err(error("truncated subjects"));
217 }
218 Ok(count)
219 }
220 fn vector(&mut self, length: usize) -> Result<Vec<u8>, String> {
221 if length > self.remaining() {
222 return Err(error("truncated object id"));
223 }
224 let bytes = self.take(length)?;
225 let mut value = Vec::new();
226 reserve(&mut value, length)?;
227 value.extend_from_slice(bytes);
228 Ok(value)
229 }
230 fn finish(self) -> Result<(), String> {
231 if self.position == self.bytes.len() {
232 Ok(())
233 } else {
234 Err(error("trailing bytes"))
235 }
236 }
237}
238fn clone_fallible<T: Clone>(values: &[T]) -> Result<Vec<T>, String> {
239 let mut copy = Vec::new();
240 reserve(&mut copy, values.len())?;
241 copy.extend_from_slice(values);
242 Ok(copy)
243}
244fn parse_authorizations(reader: &mut Reader<'_>) -> Result<Authorizations, String> {
245 let owner_count = reader.count(13)?;
246 let mut owners = Vec::new();
247 reserve(&mut owners, owner_count)?;
248 for _ in 0..owner_count {
249 owners.push(match reader.byte()? {
250 1 => OwnerSubject::User(UserId::from_tx_id(TxId::from_bytes(reader.array()?))),
251 2 => OwnerSubject::Group(GroupId::new(TxId::from_bytes(reader.array()?))),
252 _ => return Err(error("invalid owner kind")),
253 });
254 }
255 let viewer_count = reader.count(13)?;
256 let mut viewers = Vec::new();
257 reserve(&mut viewers, viewer_count)?;
258 for _ in 0..viewer_count {
259 viewers.push(match reader.byte()? {
260 1 => ViewerSubject::User(UserId::from_tx_id(TxId::from_bytes(reader.array()?))),
261 2 => ViewerSubject::Group(GroupId::new(TxId::from_bytes(reader.array()?))),
262 3 => ViewerSubject::Model(ModelId::from_bytes(reader.array()?)),
263 _ => return Err(error("invalid viewer kind")),
264 });
265 }
266 let canonical = Authorizations::new(clone_fallible(&owners)?, clone_fallible(&viewers)?)?;
267 if canonical.owners() != owners.as_slice() || canonical.viewers() != viewers.as_slice() {
268 return Err(error("noncanonical authorizations"));
269 }
270 Ok(canonical)
271}
272pub fn decode(payload: &[u8]) -> Result<([u8; 16], AccessAction), String> {
273 let mut reader = Reader::new(payload);
274 if reader.byte()? != 1 {
275 return Err(error("unsupported version"));
276 }
277 let kind = reader.byte()?;
278 let operation_id = reader.array()?;
279 let action = match kind {
280 1 => {
281 let subsystem = SubsystemId::from_bytes(reader.array()?)
282 .map_err(|value| format!("invalid subsystem: {value}"))?;
283 let object_length = reader.length()?;
284 let target = Target::new(subsystem, reader.vector(object_length)?);
285 let authorizations = parse_authorizations(&mut reader)?;
286 AccessAction::Create {
287 target,
288 authorizations,
289 }
290 }
291 2 => {
292 let access_id = AccessId::new(TxId::from_bytes(reader.array()?));
293 let actor = UserId::from_tx_id(TxId::from_bytes(reader.array()?));
294 let groups_revision = match reader.byte()? {
295 0 => None,
296 1 => Some(TxId::from_bytes(reader.array()?)),
297 _ => return Err(error("invalid revision presence")),
298 };
299 let witness = match reader.byte()? {
300 1 => OwnerWitness::User,
301 2 => OwnerWitness::Group(GroupId::new(TxId::from_bytes(reader.array()?))),
302 _ => return Err(error("invalid witness kind")),
303 };
304 let authorizations = parse_authorizations(&mut reader)?;
305 AccessAction::Replace {
306 access_id,
307 actor,
308 groups_revision,
309 witness,
310 authorizations,
311 }
312 }
313 _ => return Err(error("invalid action kind")),
314 };
315 reader.finish()?;
316 Ok((operation_id, action))
317}
318#[cfg(test)]
319mod tests {
320 use super::*;
321 fn tx(value: u8) -> TxId {
322 TxId::from_bytes([value; 12])
323 }
324 fn user(value: u8) -> UserId {
325 UserId::from_tx_id(tx(value))
326 }
327 fn group(value: u8) -> GroupId {
328 GroupId::new(tx(value))
329 }
330 fn authorizations() -> Authorizations {
331 Authorizations::new(
332 vec![OwnerSubject::User(user(1)), OwnerSubject::Group(group(2))],
333 vec![
334 ViewerSubject::User(user(3)),
335 ViewerSubject::Group(group(4)),
336 ViewerSubject::Model(ModelId::from_bytes([5; 32])),
337 ],
338 )
339 .unwrap()
340 }
341 fn raw_authorizations(owners: &[(u8, u8)], viewers: &[(u8, u8)]) -> Vec<u8> {
342 let subsystem = SubsystemId::from_str("s").unwrap();
343 let mut payload = vec![1, 1];
344 payload.extend_from_slice(&[0; 16]);
345 payload.extend_from_slice(subsystem.as_bytes());
346 payload.extend_from_slice(&0u64.to_le_bytes());
347 payload.extend_from_slice(&u64::try_from(owners.len()).unwrap().to_le_bytes());
348 for &(kind, id) in owners {
349 payload.push(kind);
350 payload.resize(payload.len() + 12, id);
351 }
352 payload.extend_from_slice(&u64::try_from(viewers.len()).unwrap().to_le_bytes());
353 for &(kind, id) in viewers {
354 payload.push(kind);
355 let width = if kind == 3 { 32 } else { 12 };
356 payload.resize(payload.len() + width, id);
357 }
358 payload
359 }
360 fn altered(mut payload: Vec<u8>, position: usize, value: u8) -> Vec<u8> {
361 payload[position] = value;
362 payload
363 }
364 #[test]
365 fn create_round_trip_preserves_arbitrary_object_and_padding() {
366 let subsystem = SubsystemId::from_str("padded").unwrap();
367 let object = vec![0, 255, 128, 0, 1];
368 let action = AccessAction::Create {
369 target: Target::new(subsystem, object.clone()),
370 authorizations: authorizations(),
371 };
372 let payload = encode([7; 16], &action).unwrap();
373 assert_eq!(&payload[18..38], subsystem.as_bytes());
374 assert!(payload[24..38].iter().all(|byte| *byte == 0));
375 assert_eq!(&payload[46..51], object.as_slice());
376 assert_eq!(decode(&payload).unwrap(), ([7; 16], action));
377 }
378 #[test]
379 fn replace_round_trips_direct_group_and_revision() {
380 let actions = [
381 AccessAction::Replace {
382 access_id: AccessId::new(tx(6)),
383 actor: user(1),
384 groups_revision: None,
385 witness: OwnerWitness::User,
386 authorizations: authorizations(),
387 },
388 AccessAction::Replace {
389 access_id: AccessId::new(tx(7)),
390 actor: user(1),
391 groups_revision: Some(tx(8)),
392 witness: OwnerWitness::Group(group(2)),
393 authorizations: authorizations(),
394 },
395 ];
396 for action in actions {
397 let payload = encode([9; 16], &action).unwrap();
398 assert_eq!(decode(&payload).unwrap(), ([9; 16], action));
399 }
400 }
401 #[test]
402 fn rejects_malformed_frames_and_discriminants() {
403 let valid = raw_authorizations(&[(1, 1)], &[]);
404 for end in 0..valid.len() {
405 assert!(decode(&valid[..end]).is_err());
406 }
407 assert!(decode(&altered(valid.clone(), 0, 2)).is_err());
408 assert!(decode(&altered(valid.clone(), 1, 9)).is_err());
409 let mut trailing = valid.clone();
410 trailing.push(0);
411 assert!(decode(&trailing).is_err());
412 assert!(decode(&altered(valid, 20, 1)).is_err());
413 let direct = AccessAction::Replace {
414 access_id: AccessId::new(tx(1)),
415 actor: user(1),
416 groups_revision: None,
417 witness: OwnerWitness::User,
418 authorizations: authorizations(),
419 };
420 let payload = encode([0; 16], &direct).unwrap();
421 assert!(decode(&altered(payload.clone(), 42, 2)).is_err());
422 assert!(decode(&altered(payload, 43, 9)).is_err());
423 }
424 #[test]
425 fn rejects_noncanonical_authorizations() {
426 let cases = [
427 raw_authorizations(&[], &[]),
428 raw_authorizations(&[(1, 2), (1, 1)], &[]),
429 raw_authorizations(&[(1, 1), (1, 1)], &[]),
430 raw_authorizations(&[(1, 1)], &[(1, 1)]),
431 raw_authorizations(&[(2, 1)], &[(2, 1)]),
432 raw_authorizations(&[(1, 1)], &[(2, 2), (1, 3)]),
433 raw_authorizations(&[(1, 1)], &[(3, 2), (3, 2)]),
434 raw_authorizations(&[(9, 1)], &[]),
435 raw_authorizations(&[(1, 1)], &[(9, 1)]),
436 ];
437 for payload in cases {
438 assert!(decode(&payload).is_err());
439 }
440 }
441 #[test]
442 fn rejects_overflowing_lengths_and_counts() {
443 let valid = raw_authorizations(&[(1, 1)], &[]);
444 let mut object = valid.clone();
445 object[38..46].copy_from_slice(&u64::MAX.to_le_bytes());
446 assert!(decode(&object).is_err());
447 let mut owners = valid.clone();
448 owners[46..54].copy_from_slice(&u64::MAX.to_le_bytes());
449 assert!(decode(&owners).is_err());
450 let mut viewers = valid;
451 viewers[67..75].copy_from_slice(&u64::MAX.to_le_bytes());
452 assert!(decode(&viewers).is_err());
453 }
454}