Skip to main content

nmp_threading/wire/
decode.rs

1use crate::{ThreadEdge, ThreadPointer, ThreadingSnapshot, TimelineBlock};
2
3use super::fb;
4
5pub fn decode_threading_snapshot(bytes: &[u8]) -> Result<ThreadingSnapshot, String> {
6    if bytes.len() < 8 || !fb::threading_snapshot_buffer_has_identifier(bytes) {
7        return Err("missing NTHR file identifier".to_string());
8    }
9    let snapshot = fb::root_as_threading_snapshot(bytes)
10        .map_err(|err| format!("not a valid ThreadingSnapshot buffer: {err}"))?;
11    Ok(ThreadingSnapshot {
12        edges: decode_edges(snapshot.edges())?,
13        blocks: decode_blocks(snapshot.blocks())?,
14        pending_ancestor_ids: decode_ids(snapshot.pending_ancestor_ids())?,
15    })
16}
17
18fn decode_edges(
19    edges: Option<flatbuffers::Vector<'_, flatbuffers::ForwardsUOffset<fb::ThreadEdge<'_>>>>,
20) -> Result<Vec<ThreadEdge>, String> {
21    let mut out = Vec::new();
22    if let Some(edges) = edges {
23        out.reserve(edges.len());
24        for edge in edges.iter() {
25            out.push(decode_edge(edge)?);
26        }
27    }
28    Ok(out)
29}
30
31fn decode_edge(edge: fb::ThreadEdge<'_>) -> Result<ThreadEdge, String> {
32    Ok(ThreadEdge {
33        event_id: str_field(edge.event_id(), "ThreadEdge.event_id")?,
34        author_pubkey: str_field(edge.author_pubkey(), "ThreadEdge.author_pubkey")?,
35        kind: edge.kind(),
36        created_at: edge.created_at(),
37        parent: edge.parent().map(decode_pointer).transpose()?,
38        root: edge.root().map(decode_pointer).transpose()?,
39        parent_author_pubkey: edge.parent_author_pubkey().map(str::to_string),
40    })
41}
42
43fn decode_blocks(
44    blocks: Option<
45        flatbuffers::Vector<'_, flatbuffers::ForwardsUOffset<fb::TimelineBlockEntry<'_>>>,
46    >,
47) -> Result<Vec<TimelineBlock>, String> {
48    let mut out = Vec::new();
49    if let Some(blocks) = blocks {
50        out.reserve(blocks.len());
51        for block in blocks.iter() {
52            out.push(decode_block(block)?);
53        }
54    }
55    Ok(out)
56}
57
58fn decode_block(block: fb::TimelineBlockEntry<'_>) -> Result<TimelineBlock, String> {
59    match block.kind() {
60        fb::TimelineBlockKind::Standalone => Ok(TimelineBlock::Standalone {
61            id: str_field(block.standalone_id(), "TimelineBlockEntry.standalone_id")?,
62            root: block.standalone_root().map(decode_pointer).transpose()?,
63        }),
64        fb::TimelineBlockKind::Module => Ok(TimelineBlock::Module {
65            events: decode_ids(block.module_event_ids())?,
66            has_gap: block.module_has_gap(),
67            root: block.module_root().map(decode_pointer).transpose()?,
68        }),
69        other => Err(format!("unknown TimelineBlockKind: {other:?}")),
70    }
71}
72
73fn decode_pointer(pointer: fb::ThreadPointer<'_>) -> Result<ThreadPointer, String> {
74    let kind = optional_kind_num(pointer.has_kind_num(), pointer.kind_num());
75    let relay = pointer.relay().map(str::to_string);
76    match pointer.kind() {
77        fb::ThreadPointerKind::Event => Ok(ThreadPointer::Event {
78            id: str_field(pointer.id(), "ThreadPointer.id")?,
79            relay,
80            kind,
81        }),
82        fb::ThreadPointerKind::Address => Ok(ThreadPointer::Address {
83            coord: str_field(pointer.coord(), "ThreadPointer.coord")?,
84            relay,
85            kind,
86        }),
87        fb::ThreadPointerKind::External => Ok(ThreadPointer::External {
88            uri: str_field(pointer.uri(), "ThreadPointer.uri")?,
89        }),
90        other => Err(format!("unknown ThreadPointerKind: {other:?}")),
91    }
92}
93
94fn decode_ids(
95    ids: Option<flatbuffers::Vector<'_, flatbuffers::ForwardsUOffset<fb::BlockEventId<'_>>>>,
96) -> Result<Vec<String>, String> {
97    let mut out = Vec::new();
98    if let Some(ids) = ids {
99        out.reserve(ids.len());
100        for id in ids.iter() {
101            out.push(str_field(id.id(), "BlockEventId.id")?);
102        }
103    }
104    Ok(out)
105}
106
107fn str_field(value: Option<&str>, ctx: &str) -> Result<String, String> {
108    value
109        .map(str::to_string)
110        .ok_or_else(|| format!("{ctx}: missing required string field"))
111}
112
113const fn optional_kind_num(present: bool, value: u32) -> Option<u32> {
114    if present {
115        Some(value)
116    } else {
117        None
118    }
119}