pub use kcode_k1_access_profile_values::{
AuthorizationProfile, GroupId, ModelId, ProfileOwner, ProfileViewer, TxId, UserId,
};
pub fn encode_profile(profile: &AuthorizationProfile) -> Result<Vec<u8>, String> {
let owner_count = u32::try_from(profile.owners().len())
.map_err(|_| "owner count exceeds canonical encoding".to_owned())?;
let viewer_count = u32::try_from(profile.viewers().len())
.map_err(|_| "viewer count exceeds canonical encoding".to_owned())?;
let mut bytes = Vec::new();
bytes.push(1);
bytes.extend_from_slice(&owner_count.to_be_bytes());
for owner in profile.owners() {
match owner {
ProfileOwner::RequestUser => bytes.push(0),
ProfileOwner::User(user) => {
bytes.push(1);
bytes.extend_from_slice(user.as_tx_id().as_bytes());
}
ProfileOwner::Group(group) => {
bytes.push(2);
bytes.extend_from_slice(group.txid().as_bytes());
}
}
}
bytes.extend_from_slice(&viewer_count.to_be_bytes());
for viewer in profile.viewers() {
match viewer {
ProfileViewer::RequestUser => bytes.push(0),
ProfileViewer::RequestModel => bytes.push(1),
ProfileViewer::User(user) => {
bytes.push(2);
bytes.extend_from_slice(user.as_tx_id().as_bytes());
}
ProfileViewer::Group(group) => {
bytes.push(3);
bytes.extend_from_slice(group.txid().as_bytes());
}
ProfileViewer::Model(model) => {
bytes.push(4);
bytes.extend_from_slice(model.as_bytes());
}
}
}
Ok(bytes)
}
pub fn decode_profile(bytes: &[u8]) -> Result<AuthorizationProfile, String> {
let mut reader = Reader::new(bytes);
if reader.u8()? != 1 {
return Err("unknown profile encoding version".to_owned());
}
let owner_count = usize::try_from(reader.u32()?)
.map_err(|_| "owner count does not fit this platform".to_owned())?;
let mut owners = Vec::new();
for _ in 0..owner_count {
owners.push(match reader.u8()? {
0 => ProfileOwner::RequestUser,
1 => ProfileOwner::User(UserId::from_tx_id(TxId::from_bytes(reader.take()?))),
2 => ProfileOwner::Group(GroupId::new(TxId::from_bytes(reader.take()?))),
_ => return Err("unknown profile owner tag".to_owned()),
});
}
let viewer_count = usize::try_from(reader.u32()?)
.map_err(|_| "viewer count does not fit this platform".to_owned())?;
let mut viewers = Vec::new();
for _ in 0..viewer_count {
viewers.push(match reader.u8()? {
0 => ProfileViewer::RequestUser,
1 => ProfileViewer::RequestModel,
2 => ProfileViewer::User(UserId::from_tx_id(TxId::from_bytes(reader.take()?))),
3 => ProfileViewer::Group(GroupId::new(TxId::from_bytes(reader.take()?))),
4 => ProfileViewer::Model(ModelId::from_bytes(reader.take()?)),
_ => return Err("unknown profile viewer tag".to_owned()),
});
}
if !reader.finished() {
return Err("trailing bytes in profile encoding".to_owned());
}
let profile = AuthorizationProfile::new(owners, viewers)?;
if encode_profile(&profile)?.as_slice() != bytes {
return Err("profile encoding is not canonical".to_owned());
}
Ok(profile)
}
struct Reader<'a> {
bytes: &'a [u8],
offset: usize,
}
impl<'a> Reader<'a> {
const fn new(bytes: &'a [u8]) -> Self {
Self { bytes, offset: 0 }
}
fn u8(&mut self) -> Result<u8, String> {
Ok(self.take::<1>()?[0])
}
fn u32(&mut self) -> Result<u32, String> {
Ok(u32::from_be_bytes(self.take()?))
}
fn take<const N: usize>(&mut self) -> Result<[u8; N], String> {
let end = self
.offset
.checked_add(N)
.ok_or_else(|| "truncated profile encoding".to_owned())?;
let source = self
.bytes
.get(self.offset..end)
.ok_or_else(|| "truncated profile encoding".to_owned())?;
let mut output = [0; N];
output.copy_from_slice(source);
self.offset = end;
Ok(output)
}
fn finished(&self) -> bool {
self.offset == self.bytes.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::{ProfileOwner as O, ProfileViewer as V};
fn canonical_case() -> (AuthorizationProfile, Vec<u8>) {
let tx = |byte| TxId::from_bytes([byte; 12]);
let profile = AuthorizationProfile::new(
vec![
O::Group(GroupId::new(tx(3))),
O::RequestUser,
O::User(UserId::from_tx_id(tx(2))),
],
vec![
V::Model(ModelId::from_bytes([5; 32])),
V::Group(GroupId::new(tx(4))),
V::RequestUser,
V::User(UserId::from_tx_id(tx(3))),
V::RequestModel,
],
)
.expect("profile");
let bytes = [
&[1, 0, 0, 0, 3, 0, 1][..],
&[2; 12],
&[2],
&[3; 12],
&[0, 0, 0, 5, 0, 1, 2],
&[3; 12],
&[3],
&[4; 12],
&[4],
&[5; 32],
]
.concat();
(profile, bytes)
}
#[test]
fn emits_the_exact_canonical_vector() {
let (profile, expected) = canonical_case();
assert_eq!(encode_profile(&profile).expect("encoding"), expected);
}
#[test]
fn canonical_vector_round_trips() {
let (profile, bytes) = canonical_case();
assert_eq!(decode_profile(&bytes).expect("decoding"), profile);
assert_eq!(
encode_profile(&decode_profile(&bytes).unwrap()).unwrap(),
bytes
);
}
#[test]
fn rejects_truncated_input() {
assert_eq!(
decode_profile(&[]).unwrap_err(),
"truncated profile encoding"
);
assert_eq!(
decode_profile(&[1, 0, 0, 0, 1, 1]).unwrap_err(),
"truncated profile encoding"
);
}
#[test]
fn rejects_unknown_version_and_tags() {
for (input, expected) in [
(vec![2], "unknown profile encoding version"),
(vec![1, 0, 0, 0, 1, 9], "unknown profile owner tag"),
(
vec![1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 9],
"unknown profile viewer tag",
),
] {
assert_eq!(decode_profile(&input).unwrap_err(), expected);
}
}
#[test]
fn rejects_empty_owner_set() {
assert_eq!(
decode_profile(&[1, 0, 0, 0, 0, 0, 0, 0, 0]).unwrap_err(),
"authorization profile requires at least one owner"
);
}
#[test]
fn rejects_noncanonical_order_and_duplicates() {
let out_of_order = [&[1, 0, 0, 0, 2, 2][..], &[1; 12], &[0, 0, 0, 0, 0]].concat();
let duplicate = vec![1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0];
for input in [out_of_order, duplicate] {
assert_eq!(
decode_profile(&input).unwrap_err(),
"profile encoding is not canonical"
);
}
}
#[test]
fn rejects_trailing_bytes() {
assert_eq!(
decode_profile(&[1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]).unwrap_err(),
"trailing bytes in profile encoding"
);
}
}