Skip to main content

kcode_k1_access_format/
lib.rs

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}