use super::constant::{ControlMessageType, FilterType, GroupOrder};
use super::control_message::ControlMessageTrait;
use crate::model::common::location::Location;
use crate::model::common::tuple::{Tuple, TupleField};
use crate::model::common::varint::{BufMutVarIntExt, BufVarIntExt};
use crate::model::data::full_track_name::FullTrackName;
use crate::model::error::ParseError;
use crate::model::parameter::message_parameter::{
MessageParameter, deserialize_message_parameters,
};
use bytes::{Buf, BufMut, Bytes, BytesMut};
#[derive(Debug, PartialEq, Clone)]
pub struct TrackStatus {
pub request_id: u64,
pub track_namespace: Tuple,
pub track_name: TupleField,
pub subscriber_priority: u8,
pub group_order: GroupOrder,
pub forward: bool,
pub filter_type: FilterType,
pub start_location: Option<Location>,
pub end_group: Option<u64>,
pub subscribe_parameters: Vec<MessageParameter>,
}
#[allow(clippy::too_many_arguments)]
impl TrackStatus {
pub fn new_next_group_start(
request_id: u64,
track_namespace: Tuple,
track_name: TupleField,
subscriber_priority: u8,
group_order: GroupOrder,
forward: bool,
subscribe_parameters: Vec<MessageParameter>,
) -> Self {
Self {
request_id,
track_namespace,
track_name,
subscriber_priority,
group_order,
forward,
filter_type: FilterType::NextGroupStart,
start_location: None,
end_group: None,
subscribe_parameters,
}
}
pub fn new_latest_object(
request_id: u64,
track_namespace: Tuple,
track_name: TupleField,
subscriber_priority: u8,
group_order: GroupOrder,
forward: bool,
subscribe_parameters: Vec<MessageParameter>,
) -> Self {
Self {
request_id,
track_namespace,
track_name,
subscriber_priority,
group_order,
forward,
filter_type: FilterType::LatestObject,
start_location: None,
end_group: None,
subscribe_parameters,
}
}
pub fn new_absolute_start(
request_id: u64,
track_namespace: Tuple,
track_name: TupleField,
subscriber_priority: u8,
group_order: GroupOrder,
forward: bool,
start_location: Location,
subscribe_parameters: Vec<MessageParameter>,
) -> Self {
Self {
request_id,
track_namespace,
track_name,
subscriber_priority,
group_order,
forward,
filter_type: FilterType::AbsoluteStart,
start_location: Some(start_location),
end_group: None,
subscribe_parameters,
}
}
pub fn new_absolute_range(
request_id: u64,
track_namespace: Tuple,
track_name: TupleField,
subscriber_priority: u8,
group_order: GroupOrder,
forward: bool,
start_location: Location,
end_group: u64,
subscribe_parameters: Vec<MessageParameter>,
) -> Self {
assert!(
end_group >= start_location.group,
"End Group must be >= Start Group"
);
Self {
request_id,
track_namespace,
track_name,
subscriber_priority,
group_order,
forward,
filter_type: FilterType::AbsoluteRange,
start_location: Some(start_location),
end_group: Some(end_group),
subscribe_parameters,
}
}
pub fn get_full_track_name(&self) -> FullTrackName {
FullTrackName {
namespace: self.track_namespace.clone(),
name: self.track_name.clone(),
}
}
}
impl ControlMessageTrait for TrackStatus {
fn serialize(&self) -> Result<Bytes, ParseError> {
let mut buf = BytesMut::new();
buf.put_vi(ControlMessageType::TrackStatus)?;
let mut payload = BytesMut::new();
payload.put_vi(self.request_id)?;
payload.extend_from_slice(&self.track_namespace.serialize()?);
payload.put_vi(self.track_name.len())?;
payload.extend_from_slice(self.track_name.as_bytes());
payload.put_u8(self.subscriber_priority);
payload.put_u8(self.group_order as u8);
payload.put_u8(if self.forward { 1 } else { 0 });
payload.put_vi(self.filter_type)?;
match self.filter_type {
FilterType::AbsoluteStart => {
if let Some(ref loc) = self.start_location {
payload.extend_from_slice(&loc.serialize()?);
} else {
unreachable!()
}
}
FilterType::AbsoluteRange => {
if let Some(ref loc) = self.start_location {
payload.extend_from_slice(&loc.serialize()?);
}
if let Some(eg) = self.end_group {
payload.put_vi(eg)?;
}
}
_ => {}
}
payload.put_vi(self.subscribe_parameters.len())?;
for param in &self.subscribe_parameters {
payload.extend_from_slice(¶m.serialize()?);
}
let payload_len: u16 = payload
.len()
.try_into()
.map_err(|e: std::num::TryFromIntError| ParseError::CastingError {
context: "TrackStatus::serialize",
from_type: "usize",
to_type: "u16",
details: e.to_string(),
})?;
buf.put_u16(payload_len);
buf.extend_from_slice(&payload);
Ok(buf.freeze())
}
fn parse_payload(payload: &mut Bytes) -> Result<Box<Self>, ParseError> {
let request_id = payload.get_vi()?;
let track_namespace = Tuple::deserialize(payload)?;
let name_len_u64 = payload.get_vi()?;
let name_len: usize = name_len_u64
.try_into()
.map_err(|e: std::num::TryFromIntError| ParseError::CastingError {
context: "TrackStatus::parse_payload(track_name_len)",
from_type: "u64",
to_type: "usize",
details: e.to_string(),
})?;
if payload.remaining() < name_len {
return Err(ParseError::NotEnoughBytes {
context: "TrackStatus::parse_payload(track_name)",
needed: name_len,
available: payload.remaining(),
});
}
let track_name = TupleField::new(payload.copy_to_bytes(name_len));
if payload.remaining() < 1 {
return Err(ParseError::NotEnoughBytes {
context: "TrackStatus::parse_payload(subscriber_priority)",
needed: 1,
available: 0,
});
}
let subscriber_priority = payload.get_u8();
if payload.remaining() < 1 {
return Err(ParseError::NotEnoughBytes {
context: "TrackStatus::parse_payload(group_order)",
needed: 1,
available: 0,
});
}
let group_order_raw = payload.get_u8();
let group_order = GroupOrder::try_from(group_order_raw)?;
let forward_raw = payload.get_u8();
let forward = match forward_raw {
0 => false,
1 => true,
_ => {
return Err(ParseError::ProtocolViolation {
context: "TrackStatus::parse_payload(forward)",
details: format!("Invalid value: {forward_raw}"),
});
}
};
let filter_type_raw = payload.get_vi()?;
let filter_type = FilterType::try_from(filter_type_raw)?;
let mut start_location: Option<Location> = None;
let mut end_group: Option<u64> = None;
match filter_type {
FilterType::AbsoluteRange => {
start_location = Some(Location::deserialize(payload)?);
end_group = Some(payload.get_vi()?);
}
FilterType::AbsoluteStart => {
start_location = Some(Location::deserialize(payload)?);
}
FilterType::LatestObject => {}
FilterType::NextGroupStart => {}
}
let param_count = payload.get_vi()?;
let subscribe_parameters =
deserialize_message_parameters(payload, param_count, ControlMessageType::TrackStatus)?;
Ok(Box::new(TrackStatus {
request_id,
track_namespace,
track_name,
subscriber_priority,
group_order,
forward,
filter_type,
start_location,
end_group,
subscribe_parameters,
}))
}
fn get_type(&self) -> ControlMessageType {
ControlMessageType::TrackStatus
}
}
#[cfg(test)]
mod tests {
use crate::model::parameter::authorization_token::AuthorizationToken;
use super::*;
use bytes::Buf;
#[test]
fn test_roundtrip() {
let request_id = 128242;
let track_namespace = Tuple::from_utf8_path("nein/nein/nein");
let track_name = TupleField::from_utf8("${Name}");
let subscriber_priority = 31;
let group_order = GroupOrder::Original;
let forward = true;
let filter_type = FilterType::AbsoluteRange;
let start_location = Location {
group: 81,
object: 81,
};
let end_group = 25;
let subscribe_parameters = vec![MessageParameter::new_authorization_token(
AuthorizationToken::new_use_value(0, Bytes::from_static(b"test-token")),
)];
let track_status = TrackStatus {
request_id,
track_namespace,
track_name,
subscriber_priority,
group_order,
forward,
filter_type,
start_location: Some(start_location),
end_group: Some(end_group),
subscribe_parameters,
};
let mut buf = track_status.serialize().unwrap();
let msg_type = buf.get_vi().unwrap();
assert_eq!(msg_type, ControlMessageType::TrackStatus as u64);
let msg_length = buf.get_u16();
assert_eq!(msg_length as usize, buf.remaining());
let deserialized = TrackStatus::parse_payload(&mut buf).unwrap();
assert_eq!(*deserialized, track_status);
assert!(!buf.has_remaining());
}
#[test]
fn test_excess_roundtrip() {
let request_id = 128242;
let track_namespace = Tuple::from_utf8_path("nein/nein/nein");
let track_name = TupleField::from_utf8("${Name}");
let subscriber_priority = 31;
let group_order = GroupOrder::Original;
let forward = true;
let filter_type = FilterType::AbsoluteRange;
let start_location = Location {
group: 81,
object: 81,
};
let end_group = 25;
let subscribe_parameters = vec![MessageParameter::new_authorization_token(
AuthorizationToken::new_use_value(0, Bytes::from_static(b"test-token")),
)];
let track_status = TrackStatus {
request_id,
track_namespace,
track_name,
subscriber_priority,
group_order,
forward,
filter_type,
start_location: Some(start_location),
end_group: Some(end_group),
subscribe_parameters,
};
let serialized = track_status.serialize().unwrap();
let mut excess = BytesMut::new();
excess.extend_from_slice(&serialized);
excess.extend_from_slice(&[9u8, 1u8, 1u8]);
let mut buf = excess.freeze();
let msg_type = buf.get_vi().unwrap();
assert_eq!(msg_type, ControlMessageType::TrackStatus as u64);
let msg_length = buf.get_u16();
assert_eq!(msg_length as usize, buf.remaining() - 3);
let deserialized = TrackStatus::parse_payload(&mut buf).unwrap();
assert_eq!(*deserialized, track_status);
assert_eq!(buf.chunk(), &[9u8, 1u8, 1u8]);
}
#[test]
fn test_partial_message() {
let request_id = 128242;
let track_namespace = Tuple::from_utf8_path("nein/nein/nein");
let track_name = TupleField::from_utf8("${Name}");
let subscriber_priority = 31;
let group_order = GroupOrder::Original;
let forward = true;
let filter_type = FilterType::AbsoluteRange;
let start_location = Location {
group: 81,
object: 81,
};
let end_group = 25;
let subscribe_parameters = vec![MessageParameter::new_authorization_token(
AuthorizationToken::new_use_value(0, Bytes::from_static(b"test-token")),
)];
let track_status = TrackStatus {
request_id,
track_namespace,
track_name,
subscriber_priority,
group_order,
forward,
filter_type,
start_location: Some(start_location),
end_group: Some(end_group),
subscribe_parameters,
};
let mut buf = track_status.serialize().unwrap();
let msg_type = buf.get_vi().unwrap();
assert_eq!(msg_type, ControlMessageType::TrackStatus as u64);
let msg_length = buf.get_u16();
assert_eq!(msg_length as usize, buf.remaining());
let upper = buf.remaining() / 2;
let mut partial = buf.slice(..upper);
let deserialized = TrackStatus::parse_payload(&mut partial);
assert!(deserialized.is_err());
}
}