Make AtemPacket operate on `&[u8] `, add field parsing

This commit is contained in:
Sam W 2025-06-19 10:05:36 +01:00
parent 16829080db
commit d88cf9249b
5 changed files with 35 additions and 32 deletions

View File

@ -2,7 +2,7 @@
/// An uninterpreted ATEM protocol field - a 4-ascii character type plus variable length data /// An uninterpreted ATEM protocol field - a 4-ascii character type plus variable length data
pub struct RawField<'a> { pub struct RawField<'a> {
pub r#type: &'a [u8; 4], pub r#type: &'a str,
pub data: &'a [u8], pub data: &'a [u8],
} }
@ -13,7 +13,7 @@ pub struct _Ver {
} }
impl<'a> Field<'a> for _Ver { impl<'a> Field<'a> for _Ver {
const TYPE: [u8; 4] = [b'_', b'v', b'e', b'r']; const TYPE: &'static str = "_ver";
fn decode(data: &'a [u8]) -> Result<Self, FieldParsingError> { fn decode(data: &'a [u8]) -> Result<Self, FieldParsingError> {
let data = checked_len::<4>(data)?; let data = checked_len::<4>(data)?;
@ -32,7 +32,7 @@ pub struct PrvI {
} }
impl<'a> Field<'a> for PrvI { impl<'a> Field<'a> for PrvI {
const TYPE: [u8; 4] = [b'P', b'r', b'v', b'I']; const TYPE: &'static str = "PrvI";
fn decode(data: &'a [u8]) -> Result<Self, FieldParsingError> { fn decode(data: &'a [u8]) -> Result<Self, FieldParsingError> {
let data = checked_len::<8>(data)?; let data = checked_len::<8>(data)?;
Ok(Self { Ok(Self {
@ -50,7 +50,7 @@ pub struct PrgI {
} }
impl<'a> Field<'a> for PrgI { impl<'a> Field<'a> for PrgI {
const TYPE: [u8; 4] = [b'P', b'r', b'g', b'I']; const TYPE: &'static str = "PrgI";
fn decode(data: &'a [u8]) -> Result<Self, FieldParsingError> { fn decode(data: &'a [u8]) -> Result<Self, FieldParsingError> {
let data = checked_len::<4>(data)?; let data = checked_len::<4>(data)?;
Ok(Self { Ok(Self {
@ -61,10 +61,10 @@ impl<'a> Field<'a> for PrgI {
} }
pub trait Field<'a>: Sized { pub trait Field<'a>: Sized {
const TYPE: [u8; 4]; const TYPE: &'static str;
fn decode(data: &'a [u8]) -> Result<Self, FieldParsingError>; fn decode(data: &'a [u8]) -> Result<Self, FieldParsingError>;
fn try_from_raw(raw: RawField<'a>) -> Result<Self, FieldParsingError> { fn try_from_raw(raw: RawField<'a>) -> Result<Self, FieldParsingError> {
if Self::TYPE != *raw.r#type { if Self::TYPE != raw.r#type {
Err(FieldParsingError::MismatchedFieldType) Err(FieldParsingError::MismatchedFieldType)
} else { } else {
Self::decode(&raw.data) Self::decode(&raw.data)
@ -75,7 +75,6 @@ pub trait Field<'a>: Sized {
#[derive(Debug)] #[derive(Debug)]
pub enum FieldParsingError { pub enum FieldParsingError {
UnexpectedLength { expected: usize, got: usize }, UnexpectedLength { expected: usize, got: usize },
UnknownFieldType { r#type: [u8; 4] },
MismatchedFieldType, MismatchedFieldType,
} }

View File

@ -1,5 +1,7 @@
use core::{fmt::Display, str}; use core::{fmt::Display, str};
use super::atem_field::{Field, FieldParsingError, RawField};
/// The "hello" packet to start communication with the ATEM /// The "hello" packet to start communication with the ATEM
pub const COMMAND_CONNECT_HELLO: [u8; 20] = [ pub const COMMAND_CONNECT_HELLO: [u8; 20] = [
0x10, 0x14, 0x53, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x14, 0x53, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
@ -11,9 +13,10 @@ pub struct AtemPacket<T: AsRef<[u8]>> {
buf: T, buf: T,
} }
impl<'a> From<&'a [u8]> for AtemPacket<&'a [u8]> { impl<'a> TryFrom<&'a [u8]> for AtemPacket<&'a [u8]> {
fn from(buf: &'a [u8]) -> Self { type Error = AtemPacketErr;
AtemPacket { buf } fn try_from(buf: &'a [u8]) -> Result<Self, Self::Error> {
AtemPacket::new_checked(buf)
} }
} }
@ -97,10 +100,7 @@ impl<T: AsRef<[u8]>> AtemPacket<T> {
pub fn ack_reply(&self) -> Option<u16> { pub fn ack_reply(&self) -> Option<u16> {
self.has_flag(PacketFlag::AckReply) self.has_flag(PacketFlag::AckReply)
.then_some(u16::from_be_bytes([ .then_some(self.ack_number())
self.buf.as_ref()[4],
self.buf.as_ref()[5],
]))
} }
/// Return true if this packet has the given [`PacketFlag`] /// Return true if this packet has the given [`PacketFlag`]
@ -111,10 +111,19 @@ impl<T: AsRef<[u8]>> AtemPacket<T> {
/// Get an iterator over the `Field`s in this packet. /// Get an iterator over the `Field`s in this packet.
/// ///
/// Returns None if this is a packet without fields. /// Returns None if this is a packet without fields.
pub fn fields(&self) -> Fields { pub fn raw_fields(&self) -> RawFields {
// TODO: do we only ever get newsessionid during the handshake (i.e. not in a packet with fields)? // TODO: do we only ever get newsessionid during the handshake (i.e. not in a packet with fields)?
let has_fields = !self.has_flag(PacketFlag::NewSessionId); let has_fields = !self.has_flag(PacketFlag::NewSessionId);
Fields::new(if has_fields { self.body() } else { &[] }) RawFields::new(if has_fields { self.body() } else { &[] })
}
pub fn fields<'a, F: Field<'a>>(
&'a self,
) -> impl Iterator<Item = Result<F, FieldParsingError>> + use<'a, F, T> {
self.raw_fields().filter_map(|f| match F::try_from_raw(f) {
Err(FieldParsingError::MismatchedFieldType) => None,
x => Some(x),
})
} }
pub fn body(&self) -> &[u8] { pub fn body(&self) -> &[u8] {
@ -137,27 +146,21 @@ impl<T: AsRef<[u8]>> Display for AtemPacket<T> {
} }
} }
/// An ATEM protocol field - a 4-ascii character type plus variable length data /// Created by [`AtemPacket::raw_fields`]
pub struct Field<'a> { pub struct RawFields<'a> {
pub r#type: &'a str,
pub data: &'a [u8],
}
/// Created by [`AtemPacket::fields`]
pub struct Fields<'a> {
data: &'a [u8], data: &'a [u8],
// The offset of the next field in the packet // The offset of the next field in the packet
offset: usize, offset: usize,
} }
impl<'a> Fields<'a> { impl<'a> RawFields<'a> {
pub(crate) fn new(data: &'a [u8]) -> Self { pub(crate) fn new(data: &'a [u8]) -> Self {
Self { data, offset: 0 } Self { data, offset: 0 }
} }
} }
impl<'a> Iterator for Fields<'a> { impl<'a> Iterator for RawFields<'a> {
type Item = Field<'a>; type Item = RawField<'a>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let remain = self.data.len() - self.offset; let remain = self.data.len() - self.offset;
if remain == 0 { if remain == 0 {
@ -172,7 +175,7 @@ impl<'a> Iterator for Fields<'a> {
// TODO: sanity check length // TODO: sanity check length
let r#type = str::from_utf8(&self.data[self.offset + 4..=self.offset + 7]).unwrap(); let r#type = str::from_utf8(&self.data[self.offset + 4..=self.offset + 7]).unwrap();
let data = &self.data[self.offset + 8..self.offset + (length as usize)]; let data = &self.data[self.offset + 8..self.offset + (length as usize)];
self.offset += (length as usize); self.offset += length as usize;
Some(Field { r#type, data }) Some(RawField { r#type, data })
} }
} }

View File

@ -394,7 +394,7 @@ impl AtemSocket {
log::debug!("Received {}", atem_packet,); log::debug!("Received {}", atem_packet,);
log::debug!( log::debug!(
"fields: {}", "fields: {}",
atem_packet.fields().map(|f| f.r#type).join(",") atem_packet.raw_fields().map(|f| f.r#type).join(",")
); );
self.last_received_at = SystemTime::now(); self.last_received_at = SystemTime::now();

View File

@ -1,2 +1,3 @@
pub mod atem_field;
pub mod atem_packet; pub mod atem_packet;
pub mod atem_socket; pub mod atem_socket;

View File

@ -1,7 +1,7 @@
use std::{collections::VecDeque, sync::Arc}; use std::{collections::VecDeque, sync::Arc};
use crate::{ use crate::{
atem_lib::atem_packet::{AtemPacket, Fields}, atem_lib::atem_packet::{AtemPacket, RawFields},
commands::device_profile::version::{deserialize_version, DESERIALIZE_VERSION_RAW_NAME}, commands::device_profile::version::{deserialize_version, DESERIALIZE_VERSION_RAW_NAME},
enums::ProtocolVersion, enums::ProtocolVersion,
}; };
@ -32,7 +32,7 @@ pub fn deserialize_commands<T: AsRef<[u8]>>(
) -> VecDeque<Arc<dyn DeserializedCommand>> { ) -> VecDeque<Arc<dyn DeserializedCommand>> {
let mut parsed_commands: VecDeque<Arc<dyn DeserializedCommand>> = VecDeque::new(); let mut parsed_commands: VecDeque<Arc<dyn DeserializedCommand>> = VecDeque::new();
for field in Fields::new(payload.as_ref()) { for field in RawFields::new(payload.as_ref()) {
let name: &str = field.r#type.try_into().unwrap(); let name: &str = field.r#type.try_into().unwrap();
log::debug!("Received command {} with length {}", name, field.data.len(),); log::debug!("Received command {} with length {}", name, field.data.len(),);