1use super::{PeerBinding, PrepareBatch, Reader, WireError, WireLimits, prepare};
4use crate::{Prefix, Scope};
5use ozzy_proto::{GroupId, NodeId, Opcode};
6
7#[derive(Debug, Clone, Copy)]
10pub struct PublicationPart<'a> {
11 pub frames: [&'a [u8]; 3],
13 pub predecessor: Prefix,
15 pub end: Prefix,
17}
18
19struct ValidatedPublicationPart<'a> {
20 metadata: &'a [u8],
21 skip: usize,
22 payload: &'a [u8],
23 count: usize,
24 scope: &'a [u8],
25}
26
27pub const PUBLICATION_TOPIC_BYTES: usize = 16;
29pub const PUBLICATION_BYTES: usize = PUBLICATION_TOPIC_BYTES;
31
32pub fn publication_topic(group: GroupId) -> [u8; PUBLICATION_TOPIC_BYTES] {
34 *group.as_bytes()
35}
36
37pub fn encode_publication(
40 sender: NodeId,
41 template: &[&[u8]],
42 committed: Prefix,
43 metadata: &mut [u8],
44 limits: WireLimits,
45) -> Result<super::PrepareEncoding, WireError> {
46 let (packet, skip) = publication_template(sender, template, limits)?;
47 super::validate_prefix(committed)?;
48 let length = packet.metadata.len() - skip;
49 let header = ozzy_proto::Envelope {
50 opcode: Opcode::PreparePub,
51 response: false,
52 request_id: None,
53 sender,
54 session: None,
55 }
56 .encode_header(length, packet.payload.len(), limits.envelope)?;
57 if metadata.len() < length {
58 return Err(WireError::Capacity);
59 }
60 metadata[..80].copy_from_slice(&packet.metadata[..80]);
61 metadata[80..length].copy_from_slice(&packet.metadata[80 + skip..]);
62 metadata[120..128].copy_from_slice(&committed.op.0.to_be_bytes());
63 metadata[128..160].copy_from_slice(committed.digest.as_bytes());
64 Ok(super::PrepareEncoding {
65 header,
66 metadata_bytes: length,
67 payload_bytes: packet.payload.len(),
68 })
69}
70
71pub fn encode_publication_group(
77 sender: NodeId,
78 parts: &[PublicationPart<'_>],
79 committed: Prefix,
80 metadata: &mut [u8],
81 payload: &mut Vec<u8>,
82 limits: WireLimits,
83) -> Result<super::PrepareEncoding, WireError> {
84 if parts.is_empty() {
85 return Err(WireError::Limit);
86 }
87 super::validate_prefix(committed)?;
88 let mut operation_count = 0usize;
89 let mut payload_bytes = 0usize;
90 let mut normalized = Vec::with_capacity(parts.len());
91 let mut previous = parts[0].predecessor;
92 let mut scope = None;
93 for part in parts {
94 let validated = validate_publication_part(sender, part, previous, scope, limits)?;
95 scope.get_or_insert(validated.scope);
96 operation_count = operation_count
97 .checked_add(validated.count)
98 .ok_or(WireError::Limit)?;
99 payload_bytes = payload_bytes
100 .checked_add(validated.payload.len())
101 .ok_or(WireError::Limit)?;
102 if payload_bytes > limits.envelope.max_payload_bytes {
103 return Err(WireError::Limit);
104 }
105 normalized.push((validated.metadata, validated.skip, validated.payload));
106 previous = part.end;
107 }
108 let metadata_bytes = prepare::metadata_length(operation_count, 164, limits)?;
109 if metadata.len() < metadata_bytes || u32::try_from(operation_count).is_err() {
110 return Err(WireError::Capacity);
111 }
112 let (first, first_skip, _) = normalized[0];
113 metadata[..80].copy_from_slice(&first[..80]);
114 metadata[80..160].copy_from_slice(&first[80 + first_skip..160 + first_skip]);
115 metadata[120..128].copy_from_slice(&committed.op.0.to_be_bytes());
116 metadata[128..160].copy_from_slice(committed.digest.as_bytes());
117 metadata[160..164].copy_from_slice(&(operation_count as u32).to_be_bytes());
118 let mut cursor = 164;
119 payload.clear();
120 payload.reserve(payload_bytes);
121 for (batch, skip, body) in normalized {
122 let descriptors = &batch[164 + skip..];
123 metadata[cursor..cursor + descriptors.len()].copy_from_slice(descriptors);
124 cursor += descriptors.len();
125 payload.extend_from_slice(body);
126 }
127 let header = ozzy_proto::Envelope {
128 opcode: Opcode::PreparePub,
129 response: false,
130 request_id: None,
131 sender,
132 session: None,
133 }
134 .encode_header(metadata_bytes, payload_bytes, limits.envelope)?;
135 Ok(super::PrepareEncoding {
136 header,
137 metadata_bytes,
138 payload_bytes,
139 })
140}
141
142fn validate_publication_part<'a>(
143 sender: NodeId,
144 part: &PublicationPart<'a>,
145 previous: Prefix,
146 scope: Option<&[u8]>,
147 limits: WireLimits,
148) -> Result<ValidatedPublicationPart<'a>, WireError> {
149 if part.predecessor != previous {
150 return Err(WireError::Chain);
151 }
152 let count = part
153 .end
154 .op
155 .0
156 .checked_sub(part.predecessor.op.0)
157 .and_then(|count| usize::try_from(count).ok())
158 .filter(|count| *count != 0)
159 .ok_or(WireError::Chain)?;
160 let (packet, skip) = publication_template(sender, &part.frames, limits)?;
161 let common = &packet.metadata[..80];
162 if scope.is_some_and(|expected| expected != common) {
163 return Err(WireError::Scope);
164 }
165 let tail = &packet.metadata[80 + skip..];
166 let first = u64::from_be_bytes(tail[..8].try_into().unwrap());
167 let predecessor = Prefix {
168 op: crate::OpNumber(first.checked_sub(1).ok_or(WireError::Chain)?),
169 digest: crate::Digest::from_bytes(tail[8..40].try_into().unwrap()),
170 };
171 let encoded_count = u32::from_be_bytes(tail[80..84].try_into().unwrap()) as usize;
172 let expected = encoded_count
173 .checked_mul(86)
174 .and_then(|bytes| bytes.checked_add(84))
175 .ok_or(WireError::Limit)?;
176 if predecessor != part.predecessor {
177 return Err(WireError::Chain);
178 }
179 if encoded_count != count || tail.len() != expected {
180 return Err(WireError::Length);
181 }
182 let descriptors = &tail[84..];
183 let body_bytes = descriptors
184 .as_chunks::<86>()
185 .0
186 .iter()
187 .try_fold(0usize, |sum, descriptor| {
188 sum.checked_add(u32::from_be_bytes(descriptor[18..22].try_into().unwrap()) as usize)
189 })
190 .ok_or(WireError::Limit)?;
191 let last = descriptors
192 .rchunks_exact(86)
193 .next()
194 .expect("nonempty batch");
195 if body_bytes != packet.payload.len() || last[54..86] != *part.end.digest.as_bytes() {
196 return Err(WireError::Chain);
197 }
198 Ok(ValidatedPublicationPart {
199 metadata: packet.metadata,
200 skip,
201 payload: packet.payload,
202 count,
203 scope: common,
204 })
205}
206
207fn publication_template<'a>(
211 sender: NodeId,
212 frames: &[&'a [u8]],
213 limits: WireLimits,
214) -> Result<(ozzy_proto::Packet<'a>, usize), WireError> {
215 let packet = if let [header, metadata, payload] = frames
216 && header.is_empty()
217 {
218 let envelope = ozzy_proto::Envelope {
219 opcode: Opcode::PreparePub,
220 response: false,
221 request_id: None,
222 sender,
223 session: None,
224 };
225 envelope.validate_frames(metadata.len(), payload.len(), limits.envelope)?;
226 ozzy_proto::Packet {
227 envelope,
228 metadata,
229 payload,
230 }
231 } else {
232 ozzy_proto::decode_packet(frames, limits.envelope)?
233 };
234 let skip = match packet.envelope.opcode {
235 Opcode::Prepare => 0,
236 Opcode::PreparePub if frames[0].is_empty() => 0,
237 Opcode::PrepareFlow => 16,
238 _ => return Err(WireError::Peer),
239 };
240 if packet.envelope.sender != sender
241 || packet.metadata.len() < 164 + skip
242 || packet.metadata[32..48] != *sender.as_bytes()
243 {
244 return Err(WireError::Peer);
245 }
246 Ok((packet, skip))
247}
248
249pub fn decode_publication<'a>(
253 frames: &[&'a [u8]],
254 binding: PeerBinding,
255 local: NodeId,
256 scope: Scope,
257 limits: WireLimits,
258) -> Result<PrepareBatch<'a>, WireError> {
259 if frames.len() != 4
260 || frames[0].len() != PUBLICATION_BYTES
261 || binding.peer != binding.configuration.primary(scope.view)
262 || binding.peer == local
263 || frames[0][..16] != *scope.group_id.as_bytes()
264 {
265 return Err(WireError::Peer);
266 }
267 let packet = ozzy_proto::decode_packet(&frames[1..], limits.envelope)?;
268 if packet.envelope.sender != binding.peer
269 || packet.envelope.session.is_some()
270 || packet.envelope.opcode != Opcode::PreparePub
271 || packet.envelope.response
272 || packet.envelope.request_id.is_some()
273 {
274 return Err(WireError::Peer);
275 }
276 let mut reader = Reader::new(packet.metadata);
277 if reader.scope(binding)? != scope {
278 return Err(WireError::Peer);
279 }
280 prepare::decode_prepare(scope, reader, packet.payload, limits)
281}