use chrono::NaiveDateTime;
use fuchsia_bluetooth::types::Uuid;
use packet_encoding::{decodable_enum, Decodable, Encodable};
use tracing::trace;
pub use self::header_set::HeaderSet;
use self::obex_string::ObexString;
use crate::error::PacketError;
pub mod header_set;
mod obex_string;
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ConnectionIdentifier(pub(crate) u32);
impl ConnectionIdentifier {
pub fn id(&self) -> u32 {
self.0
}
}
impl TryFrom<u32> for ConnectionIdentifier {
type Error = PacketError;
fn try_from(src: u32) -> Result<Self, Self::Error> {
if src == u32::MAX {
return Err(PacketError::Reserved);
}
Ok(Self(src))
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(u8)]
pub enum ActionIdentifier {
Copy = 0x00,
MoveOrRename = 0x01,
SetPermissions = 0x02,
Vendor(u8),
}
impl ActionIdentifier {
fn is_vendor(id_raw: u8) -> bool {
id_raw >= 0x80
}
}
impl From<&ActionIdentifier> for u8 {
fn from(src: &ActionIdentifier) -> u8 {
match src {
ActionIdentifier::Copy => 0x00,
ActionIdentifier::MoveOrRename => 0x01,
ActionIdentifier::SetPermissions => 0x02,
ActionIdentifier::Vendor(v) => *v,
}
}
}
impl TryFrom<u8> for ActionIdentifier {
type Error = PacketError;
fn try_from(src: u8) -> Result<ActionIdentifier, Self::Error> {
match src {
0x00 => Ok(ActionIdentifier::Copy),
0x01 => Ok(ActionIdentifier::MoveOrRename),
0x02 => Ok(ActionIdentifier::SetPermissions),
v if ActionIdentifier::is_vendor(v) => Ok(ActionIdentifier::Vendor(v)),
_v => Err(Self::Error::Reserved),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct MimeType(String);
impl MimeType {
pub fn len(&self) -> usize {
self.0.len() + 1
}
pub fn to_be_bytes(&self) -> Vec<u8> {
let mut encoded_buf: Vec<u8> = self.0.clone().into_bytes();
encoded_buf.push(0); encoded_buf
}
}
impl TryFrom<&[u8]> for MimeType {
type Error = PacketError;
fn try_from(src: &[u8]) -> Result<Self, Self::Error> {
if src.len() == 0 {
return Err(PacketError::data("empty Type header"));
}
let mut text = String::from_utf8(src.to_vec()).map_err(PacketError::external)?;
if !text.ends_with('\0') {
return Err(PacketError::data("Type missing null terminator"));
}
let _ = text.pop();
Ok(Self(text))
}
}
impl From<String> for MimeType {
fn from(src: String) -> MimeType {
MimeType(src)
}
}
impl From<&str> for MimeType {
fn from(src: &str) -> MimeType {
MimeType(src.to_string())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct UserDefinedHeader {
identifier: u8,
value: Vec<u8>,
}
pub type TypeValue = (u8, Vec<u8>);
#[derive(Clone, Debug, PartialEq)]
pub struct TagLengthValue(Vec<TypeValue>);
impl TagLengthValue {
const MIN_TRIPLET_LENGTH_BYTES: usize = 2;
pub fn iter(&self) -> std::slice::Iter<'_, (u8, Vec<u8>)> {
self.0.iter()
}
}
impl From<Vec<TypeValue>> for TagLengthValue {
fn from(value: Vec<TypeValue>) -> Self {
Self(value)
}
}
impl Encodable for TagLengthValue {
type Error = PacketError;
fn encoded_len(&self) -> core::primitive::usize {
self.0
.iter()
.fold(0, |init, triplet| init + Self::MIN_TRIPLET_LENGTH_BYTES + triplet.1.len())
}
fn encode(&self, buf: &mut [u8]) -> core::result::Result<(), Self::Error> {
if buf.len() < self.encoded_len() {
return Err(PacketError::BufferTooSmall);
}
let mut start_index = 0;
for (tag, value) in &self.0 {
buf[start_index] = *tag;
buf[start_index + 1] = value.len() as u8;
buf[start_index + 2..start_index + 2 + value.len()].copy_from_slice(value.as_slice());
start_index += 2 + value.len();
}
Ok(())
}
}
impl TryFrom<&[u8]> for TagLengthValue {
type Error = PacketError;
fn try_from(raw_data: &[u8]) -> Result<Self, Self::Error> {
let data_len = raw_data.len();
let mut decoded_len = 0;
let mut list = vec![];
while decoded_len < data_len {
if data_len < (decoded_len + Self::MIN_TRIPLET_LENGTH_BYTES) {
return Err(PacketError::DataLength);
}
let tag: u8 = raw_data[decoded_len];
let value_len = raw_data[decoded_len + 1] as usize;
if data_len < (decoded_len + Self::MIN_TRIPLET_LENGTH_BYTES + value_len) {
return Err(PacketError::DataLength);
}
let value_start_idx = decoded_len + Self::MIN_TRIPLET_LENGTH_BYTES;
let value = (&raw_data[value_start_idx..value_start_idx + value_len]).into();
let _ = list.push((tag, value));
decoded_len += Self::MIN_TRIPLET_LENGTH_BYTES + value_len;
}
Ok(TagLengthValue(list))
}
}
decodable_enum! {
pub enum SingleResponseMode<u8, PacketError, Reserved> {
Disable = 0x00,
Enable = 0x01,
}
}
impl From<bool> for SingleResponseMode {
fn from(src: bool) -> SingleResponseMode {
if src {
SingleResponseMode::Enable
} else {
SingleResponseMode::Disable
}
}
}
impl From<SingleResponseMode> for Header {
fn from(src: SingleResponseMode) -> Header {
Header::SingleResponseMode(src)
}
}
decodable_enum! {
enum HeaderEncoding<u8, PacketError, HeaderEncoding> {
Text = 0x00,
Bytes = 0x40,
OneByte = 0x80,
FourBytes = 0xC0,
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[repr(u8)]
pub enum HeaderIdentifier {
Count = 0xC0,
Name = 0x01,
Type = 0x42,
Length = 0xC3,
TimeIso8601 = 0x44,
Time4Byte = 0xC4,
Description = 0x05,
Target = 0x46,
Http = 0x47,
Body = 0x48,
EndOfBody = 0x49,
Who = 0x4A,
ConnectionId = 0xCB,
ApplicationParameters = 0x4C,
AuthenticationChallenge = 0x4D,
AuthenticationResponse = 0x4E,
CreatorId = 0xCF,
WanUuid = 0x50,
ObjectClass = 0x51,
SessionParameters = 0x52,
SessionSequenceNumber = 0x93,
ActionId = 0x94,
DestName = 0x15,
Permissions = 0xD6,
SingleResponseMode = 0x97,
SingleResponseModeParameters = 0x98,
User(u8),
}
impl HeaderIdentifier {
fn is_user(id: u8) -> bool {
let lower_6_bits = id & 0x3f;
lower_6_bits >= 0x30 && lower_6_bits <= 0x3f
}
fn is_reserved(id: u8) -> bool {
let lower_6_bits = id & 0x3f;
lower_6_bits >= 0x19 && lower_6_bits <= 0x2f
}
fn encoding(&self) -> HeaderEncoding {
let id_raw: u8 = self.into();
HeaderEncoding::try_from(id_raw & 0xc0).expect("valid Header encoding")
}
}
impl TryFrom<u8> for HeaderIdentifier {
type Error = PacketError;
fn try_from(src: u8) -> Result<Self, Self::Error> {
match src {
0xC0 => Ok(Self::Count),
0x01 => Ok(Self::Name),
0x42 => Ok(Self::Type),
0xC3 => Ok(Self::Length),
0x44 => Ok(Self::TimeIso8601),
0xC4 => Ok(Self::Time4Byte),
0x05 => Ok(Self::Description),
0x46 => Ok(Self::Target),
0x47 => Ok(Self::Http),
0x48 => Ok(Self::Body),
0x49 => Ok(Self::EndOfBody),
0x4A => Ok(Self::Who),
0xCB => Ok(Self::ConnectionId),
0x4C => Ok(Self::ApplicationParameters),
0x4D => Ok(Self::AuthenticationChallenge),
0x4E => Ok(Self::AuthenticationResponse),
0xCF => Ok(Self::CreatorId),
0x50 => Ok(Self::WanUuid),
0x51 => Ok(Self::ObjectClass),
0x52 => Ok(Self::SessionParameters),
0x93 => Ok(Self::SessionSequenceNumber),
0x94 => Ok(Self::ActionId),
0x15 => Ok(Self::DestName),
0xD6 => Ok(Self::Permissions),
0x97 => Ok(Self::SingleResponseMode),
0x98 => Ok(Self::SingleResponseModeParameters),
id if HeaderIdentifier::is_user(id) => Ok(Self::User(id)),
id if HeaderIdentifier::is_reserved(id) => Err(Self::Error::Reserved),
id => Err(Self::Error::Identifier(id)),
}
}
}
impl Into<u8> for &HeaderIdentifier {
fn into(self) -> u8 {
match &self {
HeaderIdentifier::Count => 0xC0,
HeaderIdentifier::Name => 0x01,
HeaderIdentifier::Type => 0x42,
HeaderIdentifier::Length => 0xC3,
HeaderIdentifier::TimeIso8601 => 0x44,
HeaderIdentifier::Time4Byte => 0xC4,
HeaderIdentifier::Description => 0x05,
HeaderIdentifier::Target => 0x46,
HeaderIdentifier::Http => 0x47,
HeaderIdentifier::Body => 0x48,
HeaderIdentifier::EndOfBody => 0x49,
HeaderIdentifier::Who => 0x4A,
HeaderIdentifier::ConnectionId => 0xCB,
HeaderIdentifier::ApplicationParameters => 0x4C,
HeaderIdentifier::AuthenticationChallenge => 0x4D,
HeaderIdentifier::AuthenticationResponse => 0x4E,
HeaderIdentifier::CreatorId => 0xCF,
HeaderIdentifier::WanUuid => 0x50,
HeaderIdentifier::ObjectClass => 0x51,
HeaderIdentifier::SessionParameters => 0x52,
HeaderIdentifier::SessionSequenceNumber => 0x93,
HeaderIdentifier::ActionId => 0x94,
HeaderIdentifier::DestName => 0x15,
HeaderIdentifier::Permissions => 0xD6,
HeaderIdentifier::SingleResponseMode => 0x97,
HeaderIdentifier::SingleResponseModeParameters => 0x98,
HeaderIdentifier::User(id) => *id,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Header {
Count(u32),
Name(Option<ObexString>),
Type(MimeType),
Length(u32),
TimeIso8601(NaiveDateTime),
Time4Byte(NaiveDateTime),
Description(ObexString),
Target(Vec<u8>),
Http(Vec<u8>),
Body(Vec<u8>),
EndOfBody(Vec<u8>),
Who(Vec<u8>),
ConnectionId(ConnectionIdentifier),
ApplicationParameters(TagLengthValue),
AuthenticationChallenge(Vec<u8>),
AuthenticationResponse(Vec<u8>),
CreatorId(u32),
WanUuid(Uuid),
ObjectClass(Vec<u8>),
SessionParameters(Vec<u8>),
SessionSequenceNumber(u8),
ActionId(ActionIdentifier),
DestName(ObexString),
Permissions(u32),
SingleResponseMode(SingleResponseMode),
SingleResponseModeParameters(u8),
User(UserDefinedHeader),
}
impl Header {
const MIN_HEADER_LENGTH_BYTES: usize = 1;
const MIN_UNICODE_OR_BYTE_SEQ_LENGTH_BYTES: usize = 3;
const ISO_8601_TIME_FORMAT: &'static str = "%Y%m%dT%H%M%S";
const ISO_8601_LENGTH_BYTES: usize = 34;
pub fn identifier(&self) -> HeaderIdentifier {
match &self {
Self::Count(_) => HeaderIdentifier::Count,
Self::Name(_) => HeaderIdentifier::Name,
Self::Type(_) => HeaderIdentifier::Type,
Self::Length(_) => HeaderIdentifier::Length,
Self::TimeIso8601(_) => HeaderIdentifier::TimeIso8601,
Self::Time4Byte(_) => HeaderIdentifier::Time4Byte,
Self::Description(_) => HeaderIdentifier::Description,
Self::Target(_) => HeaderIdentifier::Target,
Self::Http(_) => HeaderIdentifier::Http,
Self::Body(_) => HeaderIdentifier::Body,
Self::EndOfBody(_) => HeaderIdentifier::EndOfBody,
Self::Who(_) => HeaderIdentifier::Who,
Self::ConnectionId(_) => HeaderIdentifier::ConnectionId,
Self::ApplicationParameters(_) => HeaderIdentifier::ApplicationParameters,
Self::AuthenticationChallenge(_) => HeaderIdentifier::AuthenticationChallenge,
Self::AuthenticationResponse(_) => HeaderIdentifier::AuthenticationResponse,
Self::CreatorId(_) => HeaderIdentifier::CreatorId,
Self::WanUuid(_) => HeaderIdentifier::WanUuid,
Self::ObjectClass(_) => HeaderIdentifier::ObjectClass,
Self::SessionParameters(_) => HeaderIdentifier::SessionParameters,
Self::SessionSequenceNumber(_) => HeaderIdentifier::SessionSequenceNumber,
Self::ActionId(_) => HeaderIdentifier::ActionId,
Self::DestName(_) => HeaderIdentifier::DestName,
Self::Permissions(_) => HeaderIdentifier::Permissions,
Self::SingleResponseMode(_) => HeaderIdentifier::SingleResponseMode,
Self::SingleResponseModeParameters(_) => HeaderIdentifier::SingleResponseModeParameters,
Self::User(UserDefinedHeader { identifier, .. }) => HeaderIdentifier::User(*identifier),
}
}
fn data_length(&self) -> usize {
use Header::*;
match &self {
SessionSequenceNumber(_)
| ActionId(_)
| SingleResponseMode(_)
| SingleResponseModeParameters(_) => 1,
Count(_) | Length(_) | ConnectionId(_) | CreatorId(_) | Permissions(_)
| Time4Byte(_) => 4,
Name(option_str) => option_str.as_ref().map_or(0, |s| s.len()),
Description(s) | DestName(s) => s.len(),
Target(b)
| Http(b)
| Body(b)
| EndOfBody(b)
| Who(b)
| AuthenticationChallenge(b)
| AuthenticationResponse(b)
| ObjectClass(b)
| SessionParameters(b) => b.len(),
ApplicationParameters(params) => params.0.iter().fold(0, |acc, param| {
acc + TagLengthValue::MIN_TRIPLET_LENGTH_BYTES + param.1.len()
}),
Type(mime_type) => mime_type.len(),
TimeIso8601(_) => Self::ISO_8601_LENGTH_BYTES,
WanUuid(_) => Uuid::BLUETOOTH_UUID_LENGTH_BYTES,
User(UserDefinedHeader { value, .. }) => value.len(),
}
}
pub fn name(s: &str) -> Self {
Self::Name(Some(s.into()))
}
pub fn empty_name() -> Self {
Self::Name(None)
}
}
impl Encodable for Header {
type Error = PacketError;
fn encoded_len(&self) -> usize {
let payload_length_encoded_bytes = match self.identifier().encoding() {
HeaderEncoding::Text | HeaderEncoding::Bytes => 2,
_ => 0,
};
Self::MIN_HEADER_LENGTH_BYTES + payload_length_encoded_bytes + self.data_length()
}
fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.len() < self.encoded_len() {
return Err(PacketError::BufferTooSmall);
}
buf[0] = (&self.identifier()).into();
let start_index = match self.identifier().encoding() {
HeaderEncoding::Text | HeaderEncoding::Bytes => {
let data_length_bytes = (self.encoded_len() as u16).to_be_bytes();
buf[Self::MIN_HEADER_LENGTH_BYTES..Self::MIN_UNICODE_OR_BYTE_SEQ_LENGTH_BYTES]
.copy_from_slice(&data_length_bytes);
Self::MIN_UNICODE_OR_BYTE_SEQ_LENGTH_BYTES
}
_ => Self::MIN_HEADER_LENGTH_BYTES,
};
use Header::*;
match &self {
Count(v)
| Length(v)
| ConnectionId(ConnectionIdentifier(v))
| CreatorId(v)
| Permissions(v) => {
buf[start_index..start_index + 4].copy_from_slice(&v.to_be_bytes());
}
Name(None) => {} Name(Some(str)) | Description(str) | DestName(str) => {
let s = str.to_be_bytes();
buf[start_index..start_index + s.len()].copy_from_slice(&s);
}
Target(src)
| Http(src)
| Body(src)
| EndOfBody(src)
| Who(src)
| AuthenticationChallenge(src)
| AuthenticationResponse(src)
| ObjectClass(src)
| SessionParameters(src) => {
let n = src.len();
buf[start_index..start_index + n].copy_from_slice(&src[..]);
}
ApplicationParameters(params) => {
params.encode(&mut buf[start_index..])?;
}
SessionSequenceNumber(v) | SingleResponseModeParameters(v) => {
buf[start_index] = *v;
}
SingleResponseMode(v) => {
buf[start_index] = v.into();
}
Type(mime_type) => {
let b = mime_type.to_be_bytes();
buf[start_index..start_index + b.len()].copy_from_slice(&b[..]);
}
ActionId(v) => {
buf[start_index] = v.into();
}
TimeIso8601(time) => {
let mut formatted = time.format(Self::ISO_8601_TIME_FORMAT).to_string();
formatted.push('Z');
let s = ObexString::from(formatted).to_be_bytes();
buf[start_index..start_index + s.len()].copy_from_slice(&s);
}
Time4Byte(time) => {
let timestamp_bytes = (time.timestamp() as u32).to_be_bytes();
buf[start_index..start_index + 4].copy_from_slice(×tamp_bytes[..])
}
WanUuid(uuid) => buf[start_index..start_index + Uuid::BLUETOOTH_UUID_LENGTH_BYTES]
.copy_from_slice(&uuid.as_be_bytes()[..]),
User(UserDefinedHeader { value, .. }) => {
let n = value.len();
buf[start_index..start_index + n].copy_from_slice(&value[..]);
}
}
Ok(())
}
}
impl Decodable for Header {
type Error = PacketError;
fn decode(buf: &[u8]) -> Result<Self, Self::Error> {
if buf.len() < Self::MIN_HEADER_LENGTH_BYTES {
return Err(PacketError::BufferTooSmall);
}
let id = HeaderIdentifier::try_from(buf[0])?;
let mut start_idx = 1;
let data_length = match id.encoding() {
HeaderEncoding::Text | HeaderEncoding::Bytes => {
if buf.len() < Self::MIN_UNICODE_OR_BYTE_SEQ_LENGTH_BYTES {
return Err(PacketError::BufferTooSmall);
}
let total_length = u16::from_be_bytes(
buf[Self::MIN_HEADER_LENGTH_BYTES..Self::MIN_UNICODE_OR_BYTE_SEQ_LENGTH_BYTES]
.try_into()
.expect("checked length"),
) as usize;
let data_length = total_length
.checked_sub(Self::MIN_UNICODE_OR_BYTE_SEQ_LENGTH_BYTES)
.ok_or(PacketError::DataLength)?;
start_idx = Self::MIN_UNICODE_OR_BYTE_SEQ_LENGTH_BYTES;
data_length
}
HeaderEncoding::OneByte => 1, HeaderEncoding::FourBytes => 4, };
trace!(?id, %data_length, "Parsed OBEX packet");
if buf.len() < start_idx + data_length {
return Err(PacketError::BufferTooSmall);
}
let data = &buf[start_idx..start_idx + data_length];
let mut out_buf = vec![0; data_length];
match id {
HeaderIdentifier::Count => {
Ok(Header::Count(u32::from_be_bytes(data[..].try_into().unwrap())))
}
HeaderIdentifier::Name => {
let name = if data.len() == 0 { None } else { Some(ObexString::try_from(data)?) };
Ok(Header::Name(name))
}
HeaderIdentifier::Type => Ok(Header::Type(MimeType::try_from(data)?)),
HeaderIdentifier::Length => {
Ok(Header::Length(u32::from_be_bytes(data[..].try_into().unwrap())))
}
HeaderIdentifier::TimeIso8601 => {
let mut time_str = ObexString::try_from(data).map(|s| s.to_string())?;
if time_str.ends_with("Z") {
let _ = time_str.pop();
}
let parsed = NaiveDateTime::parse_from_str(&time_str, Self::ISO_8601_TIME_FORMAT)
.map_err(PacketError::external)?;
Ok(Header::TimeIso8601(parsed))
}
HeaderIdentifier::Time4Byte => {
let elapsed_time_seconds = u32::from_be_bytes(data[..].try_into().unwrap());
let parsed = NaiveDateTime::from_timestamp_opt(
elapsed_time_seconds.into(),
0,
)
.ok_or_else(|| PacketError::external(anyhow::format_err!("invalid timestamp")))?;
Ok(Header::Time4Byte(parsed))
}
HeaderIdentifier::Description => Ok(Header::Description(ObexString::try_from(data)?)),
HeaderIdentifier::Target => {
out_buf.copy_from_slice(&data[..]);
Ok(Header::Target(out_buf))
}
HeaderIdentifier::Http => {
out_buf.copy_from_slice(&data[..]);
Ok(Header::Http(out_buf))
}
HeaderIdentifier::Body => {
out_buf.copy_from_slice(&data[..]);
Ok(Header::Body(out_buf))
}
HeaderIdentifier::EndOfBody => {
out_buf.copy_from_slice(&data[..]);
Ok(Header::EndOfBody(out_buf))
}
HeaderIdentifier::Who => {
out_buf.copy_from_slice(&data[..]);
Ok(Header::Who(out_buf))
}
HeaderIdentifier::ConnectionId => Ok(Header::ConnectionId(
ConnectionIdentifier::try_from(u32::from_be_bytes(data[..].try_into().unwrap()))?,
)),
HeaderIdentifier::ApplicationParameters => {
Ok(Header::ApplicationParameters(TagLengthValue::try_from(&data[..])?))
}
HeaderIdentifier::AuthenticationChallenge => {
out_buf.copy_from_slice(&data[..]);
Ok(Header::AuthenticationChallenge(out_buf))
}
HeaderIdentifier::AuthenticationResponse => {
out_buf.copy_from_slice(&data[..]);
Ok(Header::AuthenticationResponse(out_buf))
}
HeaderIdentifier::CreatorId => {
Ok(Header::CreatorId(u32::from_be_bytes(data[..].try_into().unwrap())))
}
HeaderIdentifier::WanUuid => {
let bytes: [u8; Uuid::BLUETOOTH_UUID_LENGTH_BYTES] =
data[..].try_into().map_err(|_| PacketError::BufferTooSmall)?;
Ok(Header::WanUuid(Uuid::from_be_bytes(bytes)))
}
HeaderIdentifier::ObjectClass => {
out_buf.copy_from_slice(&data[..]);
Ok(Header::ObjectClass(out_buf))
}
HeaderIdentifier::SessionParameters => {
out_buf.copy_from_slice(&data[..]);
Ok(Header::SessionParameters(out_buf))
}
HeaderIdentifier::SessionSequenceNumber => {
Ok(Header::SessionSequenceNumber(buf[start_idx]))
}
HeaderIdentifier::ActionId => {
Ok(Header::ActionId(ActionIdentifier::try_from(buf[start_idx])?))
}
HeaderIdentifier::DestName => Ok(Header::DestName(ObexString::try_from(data)?)),
HeaderIdentifier::Permissions => {
Ok(Header::Permissions(u32::from_be_bytes(data[..].try_into().unwrap())))
}
HeaderIdentifier::SingleResponseMode => {
Ok(Header::SingleResponseMode(SingleResponseMode::try_from(buf[start_idx])?))
}
HeaderIdentifier::SingleResponseModeParameters => {
Ok(Header::SingleResponseModeParameters(buf[start_idx]))
}
HeaderIdentifier::User(identifier) => {
out_buf.copy_from_slice(&data[..]);
Ok(Header::User(UserDefinedHeader { identifier, value: out_buf }))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
use chrono::{NaiveDate, NaiveTime};
#[fuchsia::test]
fn convert_action_identifier_success() {
let id_raw = 0x00;
let result = ActionIdentifier::try_from(id_raw).expect("valid identifier");
assert_eq!(result, ActionIdentifier::Copy);
let converted: u8 = (&result).into();
assert_eq!(id_raw, converted);
let vendor_id_raw = 0x85;
let result = ActionIdentifier::try_from(vendor_id_raw).expect("valid identifier");
assert_eq!(result, ActionIdentifier::Vendor(0x85));
let converted: u8 = (&result).into();
assert_eq!(vendor_id_raw, converted);
}
#[fuchsia::test]
fn convert_action_identifier_error() {
let invalid = 0x03;
assert_matches!(ActionIdentifier::try_from(invalid), Err(PacketError::Reserved));
let invalid = 0x7f;
assert_matches!(ActionIdentifier::try_from(invalid), Err(PacketError::Reserved));
}
#[fuchsia::test]
fn is_user_id() {
let ids = [0x00, 0x10, 0x29, 0x40, 0x80, 0xc0];
for id in ids {
assert!(!HeaderIdentifier::is_user(id));
}
let ids = [0x30, 0x3f, 0x71, 0x7f, 0xb5, 0xbf, 0xf0, 0xff];
for id in ids {
assert!(HeaderIdentifier::is_user(id));
}
}
#[fuchsia::test]
fn is_reserved_id() {
let ids = [0x00, 0x10, 0x30, 0x70, 0xb0, 0xf0];
for id in ids {
assert!(!HeaderIdentifier::is_reserved(id));
}
let ids = [0x19, 0x2f, 0x60, 0x6f, 0x99, 0xae, 0xd9, 0xef];
for id in ids {
assert!(HeaderIdentifier::is_reserved(id));
}
}
#[fuchsia::test]
fn valid_header_id_parsed_ok() {
let valid = 0x15;
let result = HeaderIdentifier::try_from(valid);
assert_matches!(result, Ok(HeaderIdentifier::DestName));
}
#[fuchsia::test]
fn user_header_id_is_ok() {
let user_header_id_raw = 0x33;
let result = HeaderIdentifier::try_from(user_header_id_raw);
assert_matches!(result, Ok(HeaderIdentifier::User(_)));
}
#[fuchsia::test]
fn rfa_header_id_is_reserved_error() {
let rfa_header_id_raw = 0x20;
let result = HeaderIdentifier::try_from(rfa_header_id_raw);
assert_matches!(result, Err(PacketError::Reserved));
}
#[fuchsia::test]
fn unknown_header_id_is_error() {
let unknown_header_id_raw = 0x03;
let result = HeaderIdentifier::try_from(unknown_header_id_raw);
assert_matches!(result, Err(PacketError::Identifier(_)));
}
#[fuchsia::test]
fn header_encoding_from_identifier() {
assert_eq!(HeaderIdentifier::SessionSequenceNumber.encoding(), HeaderEncoding::OneByte);
assert_eq!(HeaderIdentifier::Count.encoding(), HeaderEncoding::FourBytes);
assert_eq!(HeaderIdentifier::Name.encoding(), HeaderEncoding::Text);
assert_eq!(HeaderIdentifier::Target.encoding(), HeaderEncoding::Bytes);
}
#[fuchsia::test]
fn decode_empty_header_is_error() {
assert_matches!(Header::decode(&[]), Err(PacketError::BufferTooSmall));
}
#[fuchsia::test]
fn decode_header_no_payload_is_error() {
assert_matches!(Header::decode(&[0xc0]), Err(PacketError::BufferTooSmall));
}
#[fuchsia::test]
fn decode_byte_seq_invalid_length_is_error() {
let buf = [0x01, 0x07];
assert_matches!(Header::decode(&buf), Err(PacketError::BufferTooSmall));
let buf = [0x46, 0x05];
assert_matches!(Header::decode(&buf), Err(PacketError::BufferTooSmall));
let buf = [0x48, 0x00, 0x02];
assert_matches!(Header::decode(&buf), Err(PacketError::DataLength));
}
#[fuchsia::test]
fn decode_header_invalid_payload_is_error() {
let buf = [
0x42, 0x00, 0x0b, 0x00, 0x68, 0x00, 0x69, 0x00,
0x00, ];
assert_matches!(Header::decode(&buf), Err(PacketError::BufferTooSmall));
let buf = [
0xc3, 0x00, 0x00, 0x00, ];
assert_matches!(Header::decode(&buf), Err(PacketError::BufferTooSmall));
let buf = [
0x94, ];
assert_matches!(Header::decode(&buf), Err(PacketError::BufferTooSmall));
let buf = [
0x42, 0x00, 0x06, 0x12, 0x34, ];
assert_matches!(Header::decode(&buf), Err(PacketError::BufferTooSmall));
}
#[fuchsia::test]
fn decode_valid_header_success() {
let name_buf = [
0x01, 0x00, 0x17, 0x00, 0x54, 0x00, 0x48, 0x00, 0x49,
0x00, 0x4e, 0x00, 0x47, 0x00, 0x2e, 0x00, 0x44, 0x00, 0x4f, 0x00, 0x43, 0x00, 0x00,
];
let result = Header::decode(&name_buf).expect("can decode name header");
assert_eq!(result, Header::name("THING.DOC"));
let object_class_buf = [
0x51, 0x00, 0x0a, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, ];
let result = Header::decode(&object_class_buf).expect("can decode object class header");
assert_eq!(result, Header::ObjectClass(vec![0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06]));
let session_seq_num_buf = [
0x93, 0x05, ];
let result = Header::decode(&session_seq_num_buf).expect("can decode valid name header");
assert_eq!(result, Header::SessionSequenceNumber(5));
let connection_id_buf = [
0xcb, 0x00, 0x00, 0x12, 0x34, ];
let result = Header::decode(&connection_id_buf).expect("can decode connection id header");
assert_eq!(result, Header::ConnectionId(ConnectionIdentifier(0x1234)));
}
#[fuchsia::test]
fn decode_user_data_header_success() {
let user_buf = [
0xb3, 0x05, ];
let result = Header::decode(&user_buf).expect("can decode user header");
assert_eq!(result, Header::User(UserDefinedHeader { identifier: 0xb3, value: vec![0x05] }));
}
#[fuchsia::test]
fn decode_time_header_success() {
let utc_time_buf = [
0x44, 0x00, 0x25, 0x00, 0x32, 0x00, 0x30, 0x00, 0x32, 0x00, 0x33, 0x00, 0x30, 0x00, 0x32, 0x00, 0x32,
0x00, 0x34, 0x00, 0x54, 0x00, 0x31, 0x00, 0x32, 0x00, 0x34, 0x00, 0x31, 0x00, 0x33,
0x00, 0x30, 0x00, 0x5a, 0x00, 0x00, ];
let result = Header::decode(&utc_time_buf).expect("can decode a utc time header");
assert_matches!(result, Header::TimeIso8601(t) if t == NaiveDate::from_ymd_opt(2023, 2, 24).unwrap().and_hms_opt(12, 41, 30).unwrap());
let local_time_buf = [
0x44, 0x00, 0x23, 0x00, 0x32, 0x00, 0x30, 0x00, 0x32, 0x00, 0x33, 0x00, 0x30, 0x00, 0x32, 0x00, 0x32,
0x00, 0x34, 0x00, 0x54, 0x00, 0x31, 0x00, 0x32, 0x00, 0x34, 0x00, 0x31, 0x00, 0x33,
0x00, 0x30, 0x00, 0x00, ];
let result = Header::decode(&local_time_buf).expect("can decode a local time header");
assert_matches!(result, Header::TimeIso8601(t) if t == NaiveDate::from_ymd_opt(2023, 2, 24).unwrap().and_hms_opt(12, 41, 30).unwrap());
let timestamp_buf = [
0xc4, 0x3b, 0x9a, 0xca, 0x00, ];
let result = Header::decode(×tamp_buf).expect("can decode a timestamp header");
assert_matches!(result, Header::Time4Byte(t) if t == NaiveDate::from_ymd_opt(2001, 9, 9).unwrap().and_hms_opt(1, 46, 40).unwrap());
}
#[fuchsia::test]
fn decode_invalid_time_header_is_error() {
let invalid_utc_time_buf = [
0x44, 0x00, 0x23, 0x00, 0x32, 0x00, 0x30, 0x00, 0x32, 0x00, 0x33, 0x00, 0x30, 0x00, 0x32, 0x00, 0x32,
0x00, 0x34, 0x00, 0x31, 0x00, 0x32, 0x00, 0x34, 0x00, 0x31, 0x00, 0x33, 0x00, 0x30,
0x00, 0x5a, 0x00,
0x00, ];
assert_matches!(Header::decode(&invalid_utc_time_buf), Err(PacketError::Other(_)));
}
#[fuchsia::test]
fn decode_name_header_success() {
let empty_name_buf = [
0x01, 0x00, 0x03, ];
let result = Header::decode(&empty_name_buf).expect("can decode empty name");
assert_eq!(result, Header::Name(None));
let empty_string_name_buf = [
0x01, 0x00, 0x05, 0x00, 0x00, ];
let result = Header::decode(&empty_string_name_buf).expect("can decode empty string name");
assert_eq!(result, Header::Name(Some("".into())));
let name_buf = [
0x01, 0x00, 0x0b, 0x00, 0x44, 0x00, 0x4f, 0x00, 0x43, 0x00, 0x00, ];
let result = Header::decode(&name_buf).expect("can decode empty string name");
assert_eq!(result, Header::Name(Some("DOC".into())));
}
#[fuchsia::test]
fn decode_invalid_name_header_is_error() {
let invalid_name_buf = [
0x01, 0x00, 0x09, 0x00, 0x44, 0x00, 0x4f, 0x00, 0x43, ];
let result = Header::decode(&invalid_name_buf);
assert_matches!(result, Err(PacketError::Data(_)));
}
#[fuchsia::test]
fn decode_type_header_success() {
let type_buf = [
0x42, 0x00, 0x10, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x78, 0x2d, 0x76, 0x43, 0x61, 0x72, 0x64,
0x00, ];
let result = Header::decode(&type_buf).expect("can decode type header");
assert_eq!(result, Header::Type("text/x-vCard".into()));
let empty_type_buf = [
0x42, 0x00, 0x04, 0x00, ];
let result = Header::decode(&empty_type_buf).expect("can decode type header");
assert_eq!(result, Header::Type("".into()));
}
#[fuchsia::test]
fn decode_invalid_type_header_is_error() {
let invalid_type_buf = [
0x42, 0x00, 0x03, ];
assert_matches!(Header::decode(&invalid_type_buf), Err(PacketError::Data(_)));
let invalid_type_buf = [
0x42, 0x00, 0x0f, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x78, 0x2d, 0x76, 0x43, 0x61, 0x72,
0x64, ];
assert_matches!(Header::decode(&invalid_type_buf), Err(PacketError::Data(_)));
let invalid_string_type_buf = [
0x42, 0x00, 0x07, 0x9f, 0x92, 0x96, 0x00, ];
assert_matches!(Header::decode(&invalid_string_type_buf), Err(PacketError::Other(_)));
}
#[fuchsia::test]
fn encode_user_data_header_success() {
let user = Header::User(UserDefinedHeader { identifier: 0xb3, value: vec![0x12] });
assert_eq!(user.encoded_len(), 2);
let mut buf = vec![0; user.encoded_len()];
user.encode(&mut buf).expect("can encode");
let expected_buf = [0xb3, 0x12];
assert_eq!(buf, expected_buf);
let user = Header::User(UserDefinedHeader {
identifier: 0xf5,
value: vec![0x00, 0x01, 0x02, 0x03],
});
assert_eq!(user.encoded_len(), 5);
let mut buf = vec![0; user.encoded_len()];
user.encode(&mut buf).expect("can encode");
let expected_buf = [0xf5, 0x00, 0x01, 0x02, 0x03];
assert_eq!(buf, expected_buf);
let user = Header::User(UserDefinedHeader {
identifier: 0x38,
value: vec![0x00, 0x01, 0x00, 0x02, 0x00, 0x00],
});
assert_eq!(user.encoded_len(), 9);
let mut buf = vec![0; user.encoded_len()];
user.encode(&mut buf).expect("can encode");
let expected_buf = [0x38, 0x00, 0x09, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00];
assert_eq!(buf, expected_buf);
let user =
Header::User(UserDefinedHeader { identifier: 0x70, value: vec![0x01, 0x02, 0x03] });
assert_eq!(user.encoded_len(), 6);
let mut buf = vec![0; user.encoded_len()];
user.encode(&mut buf).expect("can encode");
let expected_buf = [0x70, 0x00, 0x06, 0x01, 0x02, 0x03];
assert_eq!(buf, expected_buf);
}
#[fuchsia::test]
fn encode_time_header_success() {
let time = Header::TimeIso8601(NaiveDateTime::new(
NaiveDate::from_ymd_opt(2015, 6, 3).unwrap(),
NaiveTime::from_hms_milli_opt(12, 34, 56, 0).unwrap(),
));
assert_eq!(time.encoded_len(), 37);
let mut buf = vec![0; time.encoded_len() + 2];
time.encode(&mut buf).expect("can encode");
let expected_buf = [
0x44, 0x00, 0x25, 0x00, 0x32, 0x00, 0x30, 0x00, 0x31, 0x00, 0x35, 0x00, 0x30, 0x00, 0x36, 0x00, 0x30,
0x00, 0x33, 0x00, 0x54, 0x00, 0x31, 0x00, 0x32, 0x00, 0x33, 0x00, 0x34, 0x00, 0x35,
0x00, 0x36, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, ];
assert_eq!(buf, expected_buf);
let timestamp = Header::Time4Byte(NaiveDateTime::from_timestamp_opt(1_000_000, 0).unwrap());
assert_eq!(timestamp.encoded_len(), 5);
let mut buf = vec![0; timestamp.encoded_len()];
timestamp.encode(&mut buf).expect("can encode");
let expected_buf = [
0xc4, 0x00, 0x0f, 0x42, 0x40, ];
assert_eq!(buf, expected_buf);
}
#[fuchsia::test]
fn encode_uuid_header_success() {
let uuid = Header::WanUuid(Uuid::new16(0x180d));
assert_eq!(uuid.encoded_len(), 19);
let mut buf = vec![0; uuid.encoded_len()];
uuid.encode(&mut buf).expect("can encode");
let expected_buf = [
0x50, 0x00, 0x13, 0x00, 0x00, 0x18, 0x0d, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b,
0x34, 0xfb, ];
assert_eq!(buf, expected_buf);
}
#[fuchsia::test]
fn encode_name_header_success() {
let empty_name = Header::empty_name();
assert_eq!(empty_name.encoded_len(), 3);
let mut buf = vec![0; empty_name.encoded_len()];
empty_name.encode(&mut buf).expect("can encode");
let expected_buf = [0x01, 0x00, 0x03];
assert_eq!(buf, expected_buf);
let empty_string_name = Header::name("");
assert_eq!(empty_string_name.encoded_len(), 5);
let mut buf = vec![0; empty_string_name.encoded_len()];
empty_string_name.encode(&mut buf).expect("can encode");
let expected_buf = [0x01, 0x00, 0x05, 0x00, 0x00];
assert_eq!(buf, expected_buf);
let normal_string_name = Header::name("f");
assert_eq!(normal_string_name.encoded_len(), 7);
let mut buf = vec![0; normal_string_name.encoded_len()];
normal_string_name.encode(&mut buf).expect("can encode");
let expected_buf = [0x01, 0x00, 0x07, 0x00, 0x66, 0x00, 0x00];
assert_eq!(buf, expected_buf);
}
#[fuchsia::test]
fn encode_type_header_success() {
let type_ = Header::Type("text/html".into());
assert_eq!(type_.encoded_len(), 13);
let mut buf = vec![0; type_.encoded_len()];
type_.encode(&mut buf).expect("can encode");
let expected_buf = [
0x42, 0x00, 0x0d, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x00, ];
assert_eq!(buf, expected_buf);
}
#[fuchsia::test]
fn encode_valid_header_success() {
let srm = Header::SingleResponseMode(SingleResponseMode::Disable);
assert_eq!(srm.encoded_len(), 2);
let mut buf = vec![0; srm.encoded_len()];
srm.encode(&mut buf).expect("can encode");
let expected_buf = [
0x97, 0x00, ];
assert_eq!(buf, expected_buf);
let count = Header::Count(0x1234);
assert_eq!(count.encoded_len(), 5);
let mut buf = vec![0; count.encoded_len()];
count.encode(&mut buf).expect("can encode");
let expected_buf = [
0xc0, 0x00, 0x00, 0x12, 0x34, ];
assert_eq!(buf, expected_buf);
let desc = Header::Description("obextest".into());
assert_eq!(desc.encoded_len(), 21);
let mut buf = vec![0; desc.encoded_len()];
desc.encode(&mut buf).expect("can encode");
let expected_buf = [
0x05, 0x00, 0x15, 0x00, 0x6f, 0x00, 0x62, 0x00, 0x65, 0x00, 0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73,
0x00, 0x74, 0x00, 0x00, ];
assert_eq!(buf, expected_buf);
let auth_response = Header::AuthenticationResponse(vec![0x11, 0x22]);
assert_eq!(auth_response.encoded_len(), 5);
let mut buf = vec![0; auth_response.encoded_len()];
auth_response.encode(&mut buf).expect("can encode");
let expected_buf = [
0x4e, 0x00, 0x05, 0x11, 0x22, ];
assert_eq!(buf, expected_buf);
}
#[fuchsia::test]
fn decode_application_header_success() {
#[rustfmt::skip]
let buf = [
0x4C, 0x00, 0x09, 0x29, 0x04, 0x20, 0x00, 0x01, 0x00, ];
let result = Header::decode(&buf).expect("can decode application parameters header");
assert_eq!(
result,
Header::ApplicationParameters(vec![(0x29, vec![0x20, 0x00, 0x01, 0x00])].into()),
);
#[rustfmt::skip]
let buf = [
0x4C, 0x00, 0x0B, 0x01, 0x02, 0x00, 0x01, 0x02, 0x02, 0x00, 0x02 ];
let result = Header::decode(&buf).expect("can decode application parameters header");
assert_eq!(
result,
Header::ApplicationParameters(
vec![(0x01, vec![0x00, 0x01]), (0x02, vec![0x00, 0x02]),].into()
),
);
}
#[fuchsia::test]
fn decode_application_header_fail() {
#[rustfmt::skip]
let buf = [
0x4C, 0x00, 0x09, 0x29, 0x03, 0x20, 0x00, 0x01, 0x00, ];
let _ = Header::decode(&buf).expect_err("should have failed");
#[rustfmt::skip]
let buf = [
0x4C, 0x00, 0x0B, 0x01, 0x02, 0x00, 0x01, 0x02, 0x01, 0x00, 0x02 ];
let _ = Header::decode(&buf).expect_err("should have failed");
}
#[fuchsia::test]
fn encode_application_header() {
let header =
Header::ApplicationParameters(vec![(0x29, vec![0x20, 0x00, 0x01, 0x00])].into());
let mut buf = vec![0; header.encoded_len()];
let _ = header.encode(&mut buf).expect("should encode successfully");
#[rustfmt::skip]
assert_eq!(buf, vec![
0x4C, 0x00, 0x09, 0x29, 0x04, 0x20, 0x00, 0x01, 0x00, ]);
}
}