use prost::bytes::Bytes;
use std::fmt;
const MAX_RENDERED_BYTES: usize = 32;
#[derive(Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct UnknownTemplateMessage {
pub template_id: i32,
pub payload: Bytes,
}
impl UnknownTemplateMessage {
pub fn new(template_id: i32, payload: Bytes) -> Self {
Self {
template_id,
payload,
}
}
pub fn decode_as<M: prost::Message + Default>(&self) -> Result<M, prost::DecodeError> {
M::decode(self.payload.clone())
}
pub fn payload_hex(&self) -> String {
use fmt::Write;
let mut hex = String::with_capacity(self.payload.len() * 2);
for byte in &self.payload {
let _ = write!(hex, "{byte:02x}");
}
hex
}
pub fn from_payload_hex(template_id: i32, hex: &str) -> Option<Self> {
let hex = hex.trim();
let hex = hex
.strip_prefix("0x")
.or(hex.strip_prefix("0X"))
.unwrap_or(hex);
let digits: Vec<u8> = hex
.chars()
.filter(|character| !character.is_whitespace())
.map(|character| character.to_digit(16).map(|digit| digit as u8))
.collect::<Option<_>>()?;
if digits.len() % 2 != 0 {
return None;
}
let payload: Vec<u8> = digits
.chunks_exact(2)
.map(|pair| (pair[0] << 4) | pair[1])
.collect();
Some(Self::new(template_id, Bytes::from(payload)))
}
}
impl fmt::Display for UnknownTemplateMessage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"template_id={} ({} bytes)",
self.template_id,
self.payload.len()
)?;
if self.payload.is_empty() {
return Ok(());
}
write!(f, " {}", Hex(&self.payload))
}
}
impl fmt::Debug for UnknownTemplateMessage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UnknownTemplateMessage")
.field("template_id", &self.template_id)
.field("payload_len", &self.payload.len())
.field("payload", &format_args!("{}", Hex(&self.payload)))
.finish()
}
}
struct Hex<'a>(&'a [u8]);
impl fmt::Display for Hex<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.0.iter().take(MAX_RENDERED_BYTES) {
write!(f, "{byte:02x}")?;
}
let remaining = self.0.len().saturating_sub(MAX_RENDERED_BYTES);
if remaining > 0 {
write!(f, "…+{remaining}B")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use prost::Message;
use super::*;
use crate::rti::{RequestCancelAllOrders, RithmicOrderNotification};
fn frame<M: Message>(template_id: i32, message: &M) -> UnknownTemplateMessage {
UnknownTemplateMessage {
template_id,
payload: Bytes::from(message.encode_to_vec()),
}
}
fn notification() -> RithmicOrderNotification {
RithmicOrderNotification {
template_id: 358,
basket_id: Some("9214-2".to_string()),
symbol: Some("MESU6".to_string()),
price: Some(6412.25),
..RithmicOrderNotification::default()
}
}
#[test]
fn decodes_into_a_caller_supplied_type() {
let original = notification();
let decoded: RithmicOrderNotification = frame(358, &original)
.decode_as()
.expect("payload round-trips into the matching type");
assert_eq!(decoded, original);
}
#[test]
fn decode_as_reports_structurally_incompatible_bytes() {
let payload = vec![0x9a, 0xb6, 0x4b, 0x02, b'h', b'i'];
let frame = UnknownTemplateMessage {
template_id: 358,
payload: Bytes::from(payload),
};
assert!(frame.decode_as::<RequestCancelAllOrders>().is_err());
}
#[test]
fn decode_as_can_succeed_against_the_wrong_type() {
let decoded = frame(358, ¬ification())
.decode_as::<RequestCancelAllOrders>()
.expect("unknown fields are skipped, so this decodes");
assert_eq!(decoded.template_id, 358);
assert_eq!(decoded.account_id, None);
}
#[test]
fn payload_hex_round_trips_verbatim() {
let captured = frame(358, ¬ification());
let hex = captured.payload_hex();
assert_eq!(hex.len(), captured.payload.len() * 2);
assert!(hex.chars().all(|character| character.is_ascii_hexdigit()));
let replayed = UnknownTemplateMessage::from_payload_hex(358, &hex)
.expect("payload_hex output parses back");
assert_eq!(replayed, captured);
}
#[test]
fn from_payload_hex_tolerates_copy_paste() {
let expected = UnknownTemplateMessage {
template_id: 358,
payload: Bytes::from_static(&[0xde, 0xad, 0xbe, 0xef]),
};
for input in [
"deadbeef",
"DEADBEEF",
"0xdeadbeef",
" dead beef\n",
"dead\nbeef",
] {
assert_eq!(
UnknownTemplateMessage::from_payload_hex(358, input).as_ref(),
Some(&expected),
"{input:?}"
);
}
}
#[test]
fn from_payload_hex_rejects_malformed_input() {
assert_eq!(UnknownTemplateMessage::from_payload_hex(358, "abc"), None);
assert_eq!(UnknownTemplateMessage::from_payload_hex(358, "zz"), None);
}
#[test]
fn display_elides_a_long_payload() {
let frame = UnknownTemplateMessage {
template_id: 358,
payload: Bytes::from(vec![0xab; MAX_RENDERED_BYTES + 20]),
};
let rendered = frame.to_string();
assert!(
rendered.starts_with("template_id=358 (52 bytes) "),
"{rendered}"
);
assert!(rendered.ends_with("…+20B"), "{rendered}");
assert_eq!(frame.payload_hex().len(), 104);
}
}