macula_rust/record/
content_announcement.rs1use crate::cbor::Value;
6
7use super::{
8 entry, id_field, malformed, payload::is_content_id, text_field, unsigned, Record, RecordError,
9 RecordType,
10};
11
12#[derive(Debug, Clone, Default, PartialEq, Eq)]
15pub struct ContentAnnouncementOptions {
16 pub realm_id: [u8; 32],
17 pub serving_station: [u8; 32],
18 pub procedure: String,
19 pub name: String,
20 pub size: Option<u64>,
21 pub chunk_count: Option<u64>,
22 pub ttl_ms: u64,
23}
24
25pub fn new_content_announcement(
28 announcer_node: &[u8; 32],
29 mcid: &[u8],
30 opts: &ContentAnnouncementOptions,
31) -> Result<Record, RecordError> {
32 if !is_content_id(Some(&Value::Bytes(mcid.to_vec()))) {
33 return Err(RecordError::NotAContentId);
34 }
35 if opts.procedure.is_empty() {
36 return Err(malformed(
37 "a content announcement names its content procedure",
38 ));
39 }
40 let mut entries = vec![
41 entry("announcer_node", Value::Bytes(announcer_node.to_vec())),
42 entry("mcid", Value::Bytes(mcid.to_vec())),
43 entry("realm_id", Value::Bytes(opts.realm_id.to_vec())),
44 entry(
45 "serving_station",
46 Value::Bytes(opts.serving_station.to_vec()),
47 ),
48 entry("procedure", Value::text(opts.procedure.clone())),
49 ];
50 if !opts.name.is_empty() {
51 entries.push(entry("name", Value::text(opts.name.clone())));
52 }
53 if let Some(size) = opts.size {
54 entries.push(entry("size", Value::Int(i128::from(size))));
55 }
56 if let Some(chunks) = opts.chunk_count {
57 entries.push(entry("chunk_count", Value::Int(i128::from(chunks))));
58 }
59 Ok(unsigned(
60 RecordType::CONTENT_ANNOUNCEMENT,
61 Value::Map(entries),
62 opts.ttl_ms,
63 ))
64}
65
66#[derive(Debug, Clone, Default, PartialEq, Eq)]
68pub struct ContentAnnouncement {
69 pub announcer_node: [u8; 32],
70 pub mcid: Vec<u8>,
71 pub realm_id: [u8; 32],
72 pub serving_station: [u8; 32],
73 pub procedure: String,
74 pub name: String,
75 pub size: Option<u64>,
76 pub chunk_count: Option<u64>,
77}
78
79pub fn read_content_announcement(r: &Record) -> Result<ContentAnnouncement, RecordError> {
81 if r.record_type != RecordType::CONTENT_ANNOUNCEMENT {
82 return Err(malformed("not a content announcement"));
83 }
84 let p = &r.payload;
85 let optional = |name: &str| match p.get(name) {
86 Some(Value::Int(n)) if *n >= 0 => u64::try_from(*n).ok(),
87 _ => None,
88 };
89 Ok(ContentAnnouncement {
90 announcer_node: id_field(p, "announcer_node"),
91 mcid: match p.get("mcid") {
92 Some(Value::Bytes(b)) => b.clone(),
93 _ => Vec::new(),
94 },
95 realm_id: id_field(p, "realm_id"),
96 serving_station: id_field(p, "serving_station"),
97 procedure: text_field(p, "procedure"),
98 name: text_field(p, "name"),
99 size: optional("size"),
100 chunk_count: optional("chunk_count"),
101 })
102}