use sfv::{BareItem, Dictionary, FieldType, Item, Key, ListEntry, Parser};
use crate::error::Error;
pub const SIGNATURE_HEADER: &str = "signature";
pub fn parse_signature_dict(raw: &str) -> Result<Vec<(String, Vec<u8>)>, Error> {
let dict: Dictionary =
Parser::new(raw)
.parse()
.map_err(|e: sfv::Error| Error::InvalidHeader {
name: SIGNATURE_HEADER,
reason: e.to_string(),
})?;
let mut out = Vec::with_capacity(dict.len());
for (label, entry) in dict {
let item = match entry {
ListEntry::Item(item) => item,
ListEntry::InnerList(_) => {
return Err(Error::MalformedSignatureHeader(format!(
"entry `{label}` must be a byte-sequence item, not an inner list"
)));
}
};
let BareItem::ByteSequence(bytes) = item.bare_item else {
return Err(Error::MalformedSignatureHeader(format!(
"entry `{label}` must be a byte sequence"
)));
};
out.push((label.into(), bytes));
}
Ok(out)
}
#[must_use]
#[allow(
clippy::expect_used,
reason = "serialising an all-byte-sequence dictionary under validated keys cannot fail"
)]
pub fn serialise_signature_dict(entries: &[(String, Vec<u8>)]) -> String {
let mut dict = Dictionary::new();
for (label, bytes) in entries {
let key = Key::try_from(label.clone()).expect("signature label must be a valid sf-key");
dict.insert(
key,
ListEntry::Item(Item::new(BareItem::ByteSequence(bytes.clone()))),
);
}
FieldType::serialize(&dict).expect("byte-sequence dictionary is always serialisable")
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn roundtrip_single_entry() {
let sig_bytes = vec![0x01, 0x02, 0x03, 0x04];
let wire = serialise_signature_dict(&[("sig1".into(), sig_bytes.clone())]);
assert_eq!(wire, "sig1=:AQIDBA==:");
let parsed = parse_signature_dict(&wire).expect("parse");
assert_eq!(parsed, vec![("sig1".into(), sig_bytes)]);
}
#[test]
fn roundtrip_multiple_entries_preserves_order() {
let entries = vec![
("sig1".to_owned(), vec![0u8; 32]),
("sig2".to_owned(), vec![0xFFu8; 16]),
];
let wire = serialise_signature_dict(&entries);
let parsed = parse_signature_dict(&wire).expect("parse");
assert_eq!(parsed, entries);
}
#[test]
fn inner_list_entry_is_rejected() {
let err = parse_signature_dict("sig1=(\"@method\")").expect_err("inner list");
assert!(matches!(err, Error::MalformedSignatureHeader(_)));
}
#[test]
fn non_byte_sequence_is_rejected() {
let err = parse_signature_dict("sig1=123").expect_err("integer");
assert!(matches!(err, Error::MalformedSignatureHeader(_)));
}
}