1use super::{
4 COMMON_BYTES, ControlEncoding, PeerBinding, Reader, WireError, WireLimits, Writer,
5 transfer::{read_source, validate_source, write_source},
6 validate_scope,
7};
8use crate::{LogSource, Scope};
9use ozzy_proto::{Envelope, LinkSessionId, NodeId, Opcode, RequestId};
10
11const METADATA_BYTES: usize = COMMON_BYTES + 16 + 72 + 8 + 4;
12
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub struct CheckpointRequest {
16 pub scope: Scope,
18 pub request_id: RequestId,
20 pub nonce: RequestId,
22 pub source: LogSource,
24 pub offset: u64,
26 pub max_bytes: u32,
28}
29
30impl CheckpointRequest {
31 fn validate(self, limits: WireLimits) -> Result<(), WireError> {
32 validate_scope(self.scope)?;
33 validate_source(self.source)?;
34 if self.request_id.as_bytes() == &[0; 16] || self.nonce.as_bytes() == &[0; 16] {
35 return Err(WireError::Correlation);
36 }
37 if self.max_bytes == 0 || self.max_bytes as usize > limits.envelope.max_payload_bytes {
38 return Err(WireError::Limit);
39 }
40 Ok(())
41 }
42}
43
44#[derive(Clone, Copy, Debug, Eq, PartialEq)]
46pub enum CheckpointMessage<'a> {
47 Request(CheckpointRequest),
49 Chunk {
51 request: CheckpointRequest,
53 bytes: &'a [u8],
55 },
56}
57
58pub fn encode_checkpoint(
60 sender: NodeId,
61 session: LinkSessionId,
62 message: CheckpointMessage<'_>,
63 output: &mut [u8],
64 limits: WireLimits,
65) -> Result<ControlEncoding, WireError> {
66 let (request, payload_bytes, response) = match message {
67 CheckpointMessage::Request(request) => (request, 0, false),
68 CheckpointMessage::Chunk { request, bytes } => {
69 if bytes.is_empty()
70 || bytes.len() > request.max_bytes as usize
71 || request.source.voter != sender
72 {
73 return Err(WireError::History);
74 }
75 (request, bytes.len(), true)
76 }
77 };
78 request.validate(limits)?;
79 let header = Envelope {
80 opcode: if response {
81 Opcode::SnapshotChunk
82 } else {
83 Opcode::SnapshotBegin
84 },
85 response,
86 request_id: Some(request.request_id),
87 sender,
88 session: Some(session),
89 }
90 .encode_header(METADATA_BYTES, payload_bytes, limits.envelope)?;
91 if output.len() < METADATA_BYTES {
92 return Err(WireError::Capacity);
93 }
94 let mut writer = Writer::new(&mut output[..METADATA_BYTES]);
95 writer.scope(request.scope, sender);
96 writer.bytes(request.nonce.as_bytes());
97 write_source(&mut writer, request.source);
98 writer.bytes(&request.offset.to_be_bytes());
99 writer.bytes(&request.max_bytes.to_be_bytes());
100 Ok(ControlEncoding {
101 header,
102 metadata_bytes: METADATA_BYTES,
103 })
104}
105
106pub(super) fn decode<'a>(
107 scope: Scope,
108 envelope: Envelope,
109 mut reader: Reader<'_>,
110 payload: &'a [u8],
111 binding: PeerBinding,
112 limits: WireLimits,
113) -> Result<CheckpointMessage<'a>, WireError> {
114 let request = CheckpointRequest {
115 scope,
116 request_id: envelope.request_id.ok_or(WireError::Correlation)?,
117 nonce: RequestId::from_bytes(reader.bytes()?),
118 source: read_source(&mut reader)?,
119 offset: u64::from_be_bytes(reader.bytes()?),
120 max_bytes: reader.u32()?,
121 };
122 reader.finish()?;
123 request.validate(limits)?;
124 binding.configuration.voter_index(request.source.voter)?;
125 match envelope.opcode {
126 Opcode::SnapshotBegin if !envelope.response && payload.is_empty() => {
127 Ok(CheckpointMessage::Request(request))
128 }
129 Opcode::SnapshotChunk
130 if envelope.response
131 && request.source.voter == binding.peer
132 && !payload.is_empty()
133 && payload.len() <= request.max_bytes as usize =>
134 {
135 Ok(CheckpointMessage::Chunk {
136 request,
137 bytes: payload,
138 })
139 }
140 _ => Err(WireError::Correlation),
141 }
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146pub enum HistoryFence {
147 Receive(crate::flow::ReceiveEpoch),
149 Fetch(RequestId),
151}
152
153#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156pub struct HistoryRetired {
157 pub scope: Scope,
159 pub fence: HistoryFence,
161 pub before: crate::Prefix,
163}
164
165pub fn encode_history_retired(
167 sender: NodeId,
168 session: LinkSessionId,
169 notice: HistoryRetired,
170 output: &mut [u8],
171 limits: WireLimits,
172) -> Result<ControlEncoding, WireError> {
173 validate_scope(notice.scope)?;
174 super::validate_prefix(notice.before)?;
175 if notice.before.op.0 == 0
176 || matches!(notice.fence, HistoryFence::Fetch(id) if id.as_bytes() == &[0; 16])
177 {
178 return Err(WireError::History);
179 }
180 let length = COMMON_BYTES + 1 + 16 + 40;
181 let header = Envelope {
182 opcode: Opcode::HistoryRetired,
183 response: false,
184 request_id: None,
185 sender,
186 session: Some(session),
187 }
188 .encode_header(length, 0, limits.envelope)?;
189 if output.len() < length {
190 return Err(WireError::Capacity);
191 }
192 let mut writer = Writer::new(&mut output[..length]);
193 writer.scope(notice.scope, sender);
194 match notice.fence {
195 HistoryFence::Receive(epoch) => {
196 writer.bytes(&[0]);
197 writer.bytes(&epoch.get().to_be_bytes());
198 }
199 HistoryFence::Fetch(id) => {
200 writer.bytes(&[1]);
201 writer.bytes(id.as_bytes());
202 }
203 }
204 writer.prefix(notice.before);
205 Ok(ControlEncoding {
206 header,
207 metadata_bytes: length,
208 })
209}
210
211pub(super) fn decode_retired(
212 scope: Scope,
213 envelope: Envelope,
214 mut reader: Reader<'_>,
215 payload: &[u8],
216) -> Result<HistoryRetired, WireError> {
217 if envelope.response || envelope.request_id.is_some() || !payload.is_empty() {
218 return Err(WireError::Correlation);
219 }
220 let tag: [u8; 1] = reader.bytes()?;
221 let id: [u8; 16] = reader.bytes()?;
222 let fence = match tag[0] {
223 0 => HistoryFence::Receive(crate::flow::ReceiveEpoch::new(u128::from_be_bytes(id))?),
224 1 if id != [0; 16] => HistoryFence::Fetch(RequestId::from_bytes(id)),
225 _ => return Err(WireError::Correlation),
226 };
227 let before = reader.prefix()?;
228 reader.finish()?;
229 super::validate_prefix(before)?;
230 if before.op.0 == 0 {
231 return Err(WireError::History);
232 }
233 Ok(HistoryRetired {
234 scope,
235 fence,
236 before,
237 })
238}