1mod known;
2mod payload;
3
4#[cfg(test)]
5mod tests_support;
6
7use super::causal::MessageId;
8use super::envelope::{MessageEnvelope, SchemaId};
9use super::error::ProtocolError;
10use super::frame::{
11 DISCONNECT_REASON_BODY_VERSION, DeliveredId, Frame, FrameType, HEADER_LEN, LINK_END_EXPORT,
12 LINK_END_IMPORT, LINK_MACHINE_LEN, LINK_OPEN_ACK_HELD, LINK_OPEN_ACK_OPENED,
13 LINK_OPEN_ACK_REFUSED, LinkEnd, LinkOffer, LinkOpenOutcome, WORKER_REGISTER_ACK_ACCEPTED,
14 WORKER_REGISTER_ACK_REJECTED, WorkerActivityDescriptor, WorkerRegisterOutcome,
15 WorkerRegistration, validate_stream,
16};
17use super::version::ProtocolVersion;
18use known::decode_known_payload;
19use payload::{
20 PayloadReader, PayloadWriter, U16_LEN, U32_LEN, U64_LEN, bytes_field_len, checked_u32_len,
21 option_string_len, option_u16_len, schema_ids_field_len, string_field_len,
22 string_vec_field_len, sum_lengths,
23};
24
25const U8_FIELD_LEN: usize = 1;
29
30pub fn encoded_len(frame: &Frame) -> Result<usize, ProtocolError> {
37 frame.validate()?;
38 let payload_len = encoded_payload_len(frame)?;
39 HEADER_LEN
40 .checked_add(payload_len)
41 .ok_or_else(|| ProtocolError::codec("encoded frame length overflowed usize"))
42}
43
44pub fn encode(frame: &Frame, buffer: &mut [u8]) -> Result<usize, ProtocolError> {
56 frame.validate()?;
57 let payload_len = encoded_payload_len(frame)?;
58 let payload_length = u32::try_from(payload_len)
59 .map_err(|_| ProtocolError::codec("payload length exceeded u32::MAX"))?;
60 let total_len = HEADER_LEN
61 .checked_add(payload_len)
62 .ok_or_else(|| ProtocolError::codec("encoded frame length overflowed usize"))?;
63
64 if buffer.len() < total_len {
65 return Err(ProtocolError::codec("output buffer is too small"));
66 }
67
68 let Some(header) = buffer.get_mut(..HEADER_LEN) else {
69 return Err(ProtocolError::codec(
70 "output buffer is too small for header",
71 ));
72 };
73 write_header(frame, payload_length, header)?;
74
75 let Some(payload) = buffer.get_mut(HEADER_LEN..total_len) else {
76 return Err(ProtocolError::codec(
77 "output buffer is too small for payload",
78 ));
79 };
80 write_payload(frame, payload)?;
81
82 Ok(total_len)
83}
84
85pub fn decode(buffer: &[u8]) -> Result<(Frame, usize), ProtocolError> {
98 if buffer.len() < HEADER_LEN {
99 return Err(ProtocolError::IncompleteHeader {
100 message: Some("buffer shorter than fixed frame header".to_owned()),
101 });
102 }
103
104 let Some(header) = buffer.get(..HEADER_LEN) else {
105 return Err(ProtocolError::IncompleteHeader {
106 message: Some("buffer shorter than fixed frame header".to_owned()),
107 });
108 };
109 let mut header_reader = PayloadReader::new(header);
110 let type_id = header_reader.read_u8()?;
111 let flags = header_reader.read_u8()?;
112 let stream_id = header_reader.read_u32()?;
113 let payload_length = header_reader.read_u32()?;
114 header_reader.finish()?;
115
116 let payload_len = usize::try_from(payload_length)
117 .map_err(|_| ProtocolError::codec("payload length cannot fit usize"))?;
118 let total_len = HEADER_LEN
119 .checked_add(payload_len)
120 .ok_or_else(|| ProtocolError::codec("decoded frame length overflowed usize"))?;
121
122 if buffer.len() < total_len {
123 return Err(ProtocolError::TruncatedPayload {
124 message: Some("buffer shorter than declared payload length".to_owned()),
125 });
126 }
127
128 let Some(payload) = buffer.get(HEADER_LEN..total_len) else {
129 return Err(ProtocolError::TruncatedPayload {
130 message: Some("buffer shorter than declared payload length".to_owned()),
131 });
132 };
133
134 let frame_type = FrameType::from(type_id);
135 let frame = decode_payload(frame_type, flags, stream_id, payload)?;
136 Ok((frame, total_len))
137}
138
139fn write_header(
140 frame: &Frame,
141 payload_length: u32,
142 buffer: &mut [u8],
143) -> Result<(), ProtocolError> {
144 let mut writer = PayloadWriter::new(buffer);
145 writer.write_u8(u8::from(frame.frame_type()))?;
146 writer.write_u8(frame.wire_flags())?;
147 writer.write_u32(frame.stream_id())?;
148 writer.write_u32(payload_length)?;
149 writer.finish()
150}
151
152fn encoded_payload_len(frame: &Frame) -> Result<usize, ProtocolError> {
153 match frame {
154 Frame::Connect { auth_token, .. } => sum_lengths(&[
155 ProtocolVersion::WIRE_LEN,
156 ProtocolVersion::WIRE_LEN,
157 bytes_field_len(auth_token)?,
158 ]),
159 Frame::ConnectAck { .. } => sum_lengths(&[ProtocolVersion::WIRE_LEN, U32_LEN]),
160 Frame::ConnectError { message, .. }
161 | Frame::SubscribeError { message, .. }
162 | Frame::PublishError { message, .. } => {
163 sum_lengths(&[U16_LEN, option_string_len(message.as_deref())?])
164 }
165 Frame::Disconnect { .. } | Frame::Ping { .. } | Frame::Pong { .. } => Ok(0),
166 Frame::DisconnectReason { reason, .. } => {
167 sum_lengths(&[U8_FIELD_LEN, U16_LEN, string_field_len(reason.name())?])
168 }
169 Frame::Subscribe {
170 channel,
171 accepted_schemas,
172 ..
173 } => sum_lengths(&[
174 string_field_len(channel)?,
175 schema_ids_field_len(accepted_schemas)?,
176 U32_LEN,
177 ]),
178 Frame::SubscribeAck { .. } => sum_lengths(&[U64_LEN, SchemaId::WIRE_LEN]),
179 Frame::Unsubscribe { .. } => Ok(U64_LEN),
180 Frame::PublishAck {
181 durable_position, ..
182 } => sum_lengths(&[U64_LEN, durable_position.map_or(0, |_| U64_LEN)]),
183 Frame::Publish {
184 channel,
185 envelope,
186 idempotency_key,
187 ..
188 } => {
189 let mut parts = vec![
190 string_field_len(channel)?,
191 envelope_bytes_field_len(envelope.encoded_len()?)?,
192 ];
193 if let Some(key) = idempotency_key {
194 parts.push(string_field_len(key)?);
195 }
196 sum_lengths(&parts)
197 }
198 Frame::ConversationOpen { .. }
199 | Frame::ConversationMessage { .. }
200 | Frame::ConversationClose { .. }
201 | Frame::ConversationError { .. } => conversation_payload_len(frame),
202 Frame::Deliver {
206 envelope, message, ..
207 } => sum_lengths(&[
208 U64_LEN,
209 envelope_bytes_field_len(envelope.encoded_len()?)?,
210 message.map_or(0, |_| U64_LEN + U64_LEN),
211 ]),
212 Frame::LinkOpen { offer, .. } => link_open_payload_len(offer),
213 Frame::LinkOpenAck { outcome, .. } => link_open_ack_payload_len(outcome),
214 Frame::LinkMessage {
215 subject, sealed, ..
216 } => sum_lengths(&[string_field_len(subject)?, bytes_field_len(sealed)?]),
217 Frame::Accept {
218 referenced_message_id,
219 ..
220 } => message_id_field_len(referenced_message_id),
221 Frame::Defer {
222 referenced_message_id,
223 reason,
224 ..
225 }
226 | Frame::Reject {
227 referenced_message_id,
228 reason,
229 ..
230 } => sum_lengths(&[
231 message_id_field_len(referenced_message_id)?,
232 option_string_len(reason.as_deref())?,
233 ]),
234 Frame::Push { payload, .. } | Frame::PushReply { payload, .. } => {
235 sum_lengths(&[U64_LEN, bytes_field_len(payload)?])
236 }
237 Frame::WorkerRegister { registration, .. } => worker_register_payload_len(registration),
238 Frame::WorkerRegisterAck { outcome, .. } => worker_register_ack_payload_len(outcome),
239 Frame::Unknown { payload, .. } => checked_u32_len(payload.len()).map(|()| payload.len()),
240 }
241}
242
243fn envelope_bytes_field_len(envelope_len: usize) -> Result<usize, ProtocolError> {
244 checked_u32_len(envelope_len)?;
245 sum_lengths(&[U32_LEN, envelope_len])
246}
247
248fn message_id_field_len(message_id: &MessageId) -> Result<usize, ProtocolError> {
249 string_field_len(message_id.as_str())
250}
251
252fn conversation_payload_len(frame: &Frame) -> Result<usize, ProtocolError> {
253 match frame {
254 Frame::ConversationOpen { subject, .. } => {
255 sum_lengths(&[U64_LEN, string_field_len(subject)?])
256 }
257 Frame::ConversationMessage { envelope, .. } => {
260 sum_lengths(&[U64_LEN, envelope_bytes_field_len(envelope.encoded_len()?)?])
261 }
262 Frame::ConversationClose {
263 reason_code,
264 message,
265 ..
266 } => sum_lengths(&[
267 U64_LEN,
268 option_u16_len(*reason_code),
269 option_string_len(message.as_deref())?,
270 ]),
271 Frame::ConversationError { message, .. } => {
272 sum_lengths(&[U64_LEN, U16_LEN, option_string_len(message.as_deref())?])
273 }
274 _ => Err(ProtocolError::codec(
275 "frame type was not a conversation frame",
276 )),
277 }
278}
279
280fn link_open_payload_len(offer: &LinkOffer) -> Result<usize, ProtocolError> {
281 sum_lengths(&[
282 string_field_len(&offer.link_id)?,
283 U8_FIELD_LEN,
284 LINK_MACHINE_LEN,
285 bytes_field_len(&offer.handshake)?,
286 ])
287}
288
289fn link_open_ack_payload_len(outcome: &LinkOpenOutcome) -> Result<usize, ProtocolError> {
290 match outcome {
291 LinkOpenOutcome::Opened { handshake } => {
292 sum_lengths(&[U8_FIELD_LEN, bytes_field_len(handshake)?])
293 }
294 LinkOpenOutcome::Held { grant, draft } => sum_lengths(&[
295 U8_FIELD_LEN,
296 string_field_len(grant)?,
297 string_field_len(draft)?,
298 ]),
299 LinkOpenOutcome::Refused { reason } => {
300 sum_lengths(&[U8_FIELD_LEN, string_field_len(reason)?])
301 }
302 }
303}
304
305fn worker_register_payload_len(registration: &WorkerRegistration) -> Result<usize, ProtocolError> {
306 sum_lengths(&[
307 string_vec_field_len(®istration.namespaces)?,
308 string_field_len(®istration.task_queue)?,
309 option_string_len(registration.node.as_deref())?,
310 string_vec_field_len(®istration.activity_types)?,
311 string_field_len(®istration.identity)?,
312 activity_descriptors_field_len(®istration.activities)?,
313 ])
314}
315
316fn activity_descriptors_field_len(
317 activities: &[WorkerActivityDescriptor],
318) -> Result<usize, ProtocolError> {
319 checked_u32_len(activities.len())?;
320 let mut total = U32_LEN;
321 for activity in activities {
322 total = sum_lengths(&[
323 total,
324 string_field_len(&activity.name)?,
325 string_field_len(&activity.input_schema_json)?,
326 string_field_len(&activity.output_schema_json)?,
327 ])?;
328 }
329 Ok(total)
330}
331
332fn worker_register_ack_payload_len(
333 outcome: &WorkerRegisterOutcome,
334) -> Result<usize, ProtocolError> {
335 match outcome {
336 WorkerRegisterOutcome::Accepted => Ok(U8_FIELD_LEN),
337 WorkerRegisterOutcome::Rejected { reason } => {
338 sum_lengths(&[U8_FIELD_LEN, string_field_len(reason)?])
339 }
340 }
341}
342
343fn write_handshake_payload(
344 frame: &Frame,
345 writer: &mut PayloadWriter<'_>,
346) -> Result<(), ProtocolError> {
347 match frame {
348 Frame::Connect {
349 min_version,
350 max_version,
351 auth_token,
352 ..
353 } => {
354 writer.write_slice(&min_version.to_wire_bytes())?;
355 writer.write_slice(&max_version.to_wire_bytes())?;
356 writer.write_bytes_field(auth_token)
357 }
358 Frame::ConnectAck {
359 selected_version,
360 capabilities,
361 ..
362 } => {
363 writer.write_slice(&selected_version.to_wire_bytes())?;
364 writer.write_u32(*capabilities)
365 }
366 _ => Err(ProtocolError::codec("frame type was not a handshake frame")),
367 }
368}
369
370fn write_pressure_payload(
371 frame: &Frame,
372 writer: &mut PayloadWriter<'_>,
373) -> Result<(), ProtocolError> {
374 match frame {
375 Frame::Accept {
376 referenced_message_id,
377 ..
378 } => writer.write_string_field(referenced_message_id.as_str()),
379 Frame::Defer {
380 referenced_message_id,
381 reason,
382 ..
383 }
384 | Frame::Reject {
385 referenced_message_id,
386 reason,
387 ..
388 } => {
389 writer.write_string_field(referenced_message_id.as_str())?;
390 writer.write_optional_string(reason.as_deref())
391 }
392 _ => Err(ProtocolError::codec("frame type was not a pressure frame")),
393 }
394}
395
396fn write_publish(frame: &Frame, writer: &mut PayloadWriter<'_>) -> Result<(), ProtocolError> {
397 match frame {
398 Frame::Publish {
399 channel,
400 envelope,
401 idempotency_key,
402 ..
403 } => {
404 writer.write_string_field(channel)?;
405 writer.write_bytes_field(&envelope.serialize()?)?;
406 if let Some(key) = idempotency_key {
409 writer.write_string_field(key)?;
410 }
411 Ok(())
412 }
413 Frame::PublishAck {
414 message_id,
415 durable_position,
416 ..
417 } => {
418 writer.write_u64(*message_id)?;
419 writer.write_flagged_u64(*durable_position)
420 }
421 _ => Err(ProtocolError::codec("frame type was not a publish frame")),
422 }
423}
424
425fn write_conversation_payload(
426 frame: &Frame,
427 writer: &mut PayloadWriter<'_>,
428) -> Result<(), ProtocolError> {
429 match frame {
430 Frame::ConversationOpen {
431 conversation_id,
432 subject,
433 ..
434 } => {
435 writer.write_u64(*conversation_id)?;
436 writer.write_string_field(subject)
437 }
438 Frame::ConversationMessage {
439 conversation_id,
440 envelope,
441 ..
442 } => write_u64_prefixed_envelope(writer, *conversation_id, envelope),
443 Frame::ConversationClose {
444 conversation_id,
445 reason_code,
446 message,
447 ..
448 } => {
449 writer.write_u64(*conversation_id)?;
450 writer.write_optional_u16(*reason_code)?;
451 writer.write_optional_string(message.as_deref())
452 }
453 Frame::ConversationError {
454 conversation_id,
455 reason_code,
456 message,
457 ..
458 } => {
459 writer.write_u64(*conversation_id)?;
460 writer.write_u16(*reason_code)?;
461 writer.write_optional_string(message.as_deref())
462 }
463 _ => Err(ProtocolError::codec(
464 "frame type was not a conversation frame",
465 )),
466 }
467}
468
469fn write_u64_prefixed_envelope(
472 writer: &mut PayloadWriter<'_>,
473 value: u64,
474 envelope: &MessageEnvelope,
475) -> Result<(), ProtocolError> {
476 writer.write_u64(value)?;
477 writer.write_bytes_field(&envelope.serialize()?)
478}
479
480fn write_deliver_payload(
484 writer: &mut PayloadWriter<'_>,
485 delivery_seq: u64,
486 envelope: &MessageEnvelope,
487 message: Option<DeliveredId>,
488) -> Result<(), ProtocolError> {
489 write_u64_prefixed_envelope(writer, delivery_seq, envelope)?;
490 if let Some(DeliveredId { boot, id }) = message {
491 writer.write_u64(boot)?;
492 writer.write_u64(id)?;
493 }
494 Ok(())
495}
496
497fn write_push_payload(frame: &Frame, writer: &mut PayloadWriter<'_>) -> Result<(), ProtocolError> {
498 match frame {
499 Frame::Push {
500 correlation_id,
501 payload,
502 ..
503 }
504 | Frame::PushReply {
505 correlation_id,
506 payload,
507 ..
508 } => {
509 writer.write_u64(*correlation_id)?;
510 writer.write_bytes_field(payload)
511 }
512 _ => Err(ProtocolError::codec("frame type was not a push frame")),
513 }
514}
515
516fn write_worker_register_payload(
517 registration: &WorkerRegistration,
518 writer: &mut PayloadWriter<'_>,
519) -> Result<(), ProtocolError> {
520 writer.write_string_vec_field(®istration.namespaces)?;
521 writer.write_string_field(®istration.task_queue)?;
522 writer.write_optional_string(registration.node.as_deref())?;
525 writer.write_string_vec_field(®istration.activity_types)?;
526 writer.write_string_field(®istration.identity)?;
527 let count = u32::try_from(registration.activities.len())
528 .map_err(|_| ProtocolError::codec("activity descriptor count exceeded u32::MAX"))?;
529 writer.write_u32(count)?;
530 for activity in ®istration.activities {
531 writer.write_string_field(&activity.name)?;
532 writer.write_string_field(&activity.input_schema_json)?;
533 writer.write_string_field(&activity.output_schema_json)?;
534 }
535 Ok(())
536}
537
538fn write_worker_register_ack_payload(
539 outcome: &WorkerRegisterOutcome,
540 writer: &mut PayloadWriter<'_>,
541) -> Result<(), ProtocolError> {
542 match outcome {
543 WorkerRegisterOutcome::Accepted => writer.write_u8(WORKER_REGISTER_ACK_ACCEPTED),
544 WorkerRegisterOutcome::Rejected { reason } => {
545 writer.write_u8(WORKER_REGISTER_ACK_REJECTED)?;
546 writer.write_string_field(reason)
547 }
548 }
549}
550
551const fn link_end_byte(end: LinkEnd) -> u8 {
553 match end {
554 LinkEnd::Export => LINK_END_EXPORT,
555 LinkEnd::Import => LINK_END_IMPORT,
556 }
557}
558
559fn write_link_open_payload(
560 offer: &LinkOffer,
561 writer: &mut PayloadWriter<'_>,
562) -> Result<(), ProtocolError> {
563 writer.write_string_field(&offer.link_id)?;
564 writer.write_u8(link_end_byte(offer.end))?;
565 writer.write_slice(&offer.machine)?;
567 writer.write_bytes_field(&offer.handshake)
568}
569
570fn write_link_open_ack_payload(
571 outcome: &LinkOpenOutcome,
572 writer: &mut PayloadWriter<'_>,
573) -> Result<(), ProtocolError> {
574 match outcome {
575 LinkOpenOutcome::Opened { handshake } => {
576 writer.write_u8(LINK_OPEN_ACK_OPENED)?;
577 writer.write_bytes_field(handshake)
578 }
579 LinkOpenOutcome::Held { grant, draft } => {
580 writer.write_u8(LINK_OPEN_ACK_HELD)?;
581 writer.write_string_field(grant)?;
582 writer.write_string_field(draft)
583 }
584 LinkOpenOutcome::Refused { reason } => {
585 writer.write_u8(LINK_OPEN_ACK_REFUSED)?;
586 writer.write_string_field(reason)
587 }
588 }
589}
590
591fn write_payload(frame: &Frame, buffer: &mut [u8]) -> Result<(), ProtocolError> {
592 let mut writer = PayloadWriter::new(buffer);
593 match frame {
594 Frame::Connect { .. } | Frame::ConnectAck { .. } => {
595 write_handshake_payload(frame, &mut writer)?;
596 }
597 Frame::ConnectError {
598 reason_code,
599 message,
600 ..
601 }
602 | Frame::SubscribeError {
603 reason_code,
604 message,
605 ..
606 }
607 | Frame::PublishError {
608 reason_code,
609 message,
610 ..
611 } => {
612 writer.write_u16(*reason_code)?;
613 writer.write_optional_string(message.as_deref())?;
614 }
615 Frame::Disconnect { .. } | Frame::Ping { .. } | Frame::Pong { .. } => {}
616 Frame::DisconnectReason { reason, .. } => {
617 writer.write_u8(DISCONNECT_REASON_BODY_VERSION)?;
620 writer.write_u16(reason.code())?;
621 writer.write_string_field(reason.name())?;
622 }
623 Frame::Subscribe {
624 channel,
625 accepted_schemas,
626 max_in_flight,
627 ..
628 } => {
629 writer.write_string_field(channel)?;
630 writer.write_schema_ids_field(accepted_schemas)?;
631 writer.write_u32(*max_in_flight)?;
632 }
633 Frame::SubscribeAck {
634 subscription_id,
635 selected_schema,
636 ..
637 } => {
638 writer.write_u64(*subscription_id)?;
639 writer.write_schema_id(*selected_schema)?;
640 }
641 Frame::Unsubscribe {
642 subscription_id, ..
643 } => writer.write_u64(*subscription_id)?,
644 Frame::Publish { .. } | Frame::PublishAck { .. } => write_publish(frame, &mut writer)?,
645 Frame::ConversationOpen { .. }
646 | Frame::ConversationMessage { .. }
647 | Frame::ConversationClose { .. }
648 | Frame::ConversationError { .. } => write_conversation_payload(frame, &mut writer)?,
649 Frame::Accept { .. } | Frame::Defer { .. } | Frame::Reject { .. } => {
650 write_pressure_payload(frame, &mut writer)?;
651 }
652 Frame::Push { .. } | Frame::PushReply { .. } => {
653 write_push_payload(frame, &mut writer)?;
654 }
655 Frame::Deliver {
656 delivery_seq,
657 envelope,
658 message,
659 ..
660 } => write_deliver_payload(&mut writer, *delivery_seq, envelope, *message)?,
661 Frame::WorkerRegister { registration, .. } => {
662 write_worker_register_payload(registration, &mut writer)?;
663 }
664 Frame::WorkerRegisterAck { outcome, .. } => {
665 write_worker_register_ack_payload(outcome, &mut writer)?;
666 }
667 Frame::LinkOpen { offer, .. } => write_link_open_payload(offer, &mut writer)?,
668 Frame::LinkOpenAck { outcome, .. } => write_link_open_ack_payload(outcome, &mut writer)?,
669 Frame::LinkMessage {
670 subject, sealed, ..
671 } => {
672 writer.write_string_field(subject)?;
673 writer.write_bytes_field(sealed)?;
674 }
675 Frame::Unknown { payload, .. } => writer.write_slice(payload)?,
676 }
677 writer.finish()
678}
679
680fn decode_payload(
681 frame_type: FrameType,
682 flags: u8,
683 stream_id: u32,
684 payload: &[u8],
685) -> Result<Frame, ProtocolError> {
686 if let FrameType::Unknown(type_id) = frame_type {
687 return Ok(Frame::Unknown {
688 type_id,
689 flags,
690 stream_id,
691 payload: payload.to_vec(),
692 });
693 }
694
695 validate_stream(frame_type, stream_id)?;
696 decode_known_payload(frame_type, flags, stream_id, payload)
697}
698
699#[cfg(test)]
700mod tests;