| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| failure::Fail, |
| fuchsia_syslog::fx_log_warn, |
| fuchsia_zircon as zx, |
| std::{ |
| fmt, |
| io::{Cursor, Write}, |
| result, |
| }, |
| }; |
| |
| /// Result type for AVDTP, using avdtp::Error |
| pub type Result<T> = result::Result<T, Error>; |
| |
| /// The error type of the AVDTP library. |
| #[derive(Fail, Debug, PartialEq)] |
| pub enum Error { |
| /// The value that was sent on the wire was out of range. |
| #[fail(display = "Value was out of range")] |
| OutOfRange, |
| |
| /// The signal identifier was invalid when parsing a message. |
| #[fail(display = "Invalid signal id for {:?}: {:X?}", _0, _1)] |
| InvalidSignalId(TxLabel, u8), |
| |
| /// The header was invalid when parsing a message from the peer. |
| #[fail(display = "Invalid Header for a AVDTP message")] |
| InvalidHeader, |
| |
| /// The body format was invalid when parsing a message from the peer. |
| #[fail(display = "Failed to parse AVDTP message contents")] |
| InvalidMessage, |
| |
| /// The remote end failed to respond to this command in time. |
| #[fail(display = "Command timed out")] |
| Timeout, |
| |
| /// The Remote end rejected a command we sent (with this error code) |
| #[fail(display = "Remote end rejected the command (code = {:}", _0)] |
| RemoteRejected(u8), |
| |
| /// The Remote end rejected a set configuration or reconfigure command we sent, |
| /// indicating this ServiceCategory (code). |
| /// The indicated ServiceCategory can be retrieved using `ServiceCategory::try_from` |
| #[fail( |
| display = "Remote end rejected the command (Category = {:}, code = {:}", |
| _0, _1 |
| )] |
| RemoteConfigRejected(u8, u8), |
| |
| /// The Remote end rejected a start or suspend command we sent, indicating this SEID and error |
| /// code. |
| #[fail( |
| display = "Remote end rejected the command (SEID = {:}, code = {:}", |
| _0, _1 |
| )] |
| RemoteStreamRejected(u8, u8), |
| |
| /// When a message hasn't been implemented yet, the parser will return this. |
| #[fail(display = "Message has not been implemented yet")] |
| UnimplementedMessage, |
| |
| /// The distant peer has disconnected. |
| #[fail(display = "Peer has disconnected")] |
| PeerDisconnected, |
| |
| /// Sent if a Command Future is polled after it's already completed |
| #[fail(display = "Command Response has already been received")] |
| AlreadyReceived, |
| |
| /// Encountered an IO error setting up the channel |
| #[fail(display = "Encountered an IO error reading from the peer: {}", _0)] |
| ChannelSetup(#[cause] zx::Status), |
| |
| /// Encountered an IO error reading from the peer. |
| #[fail(display = "Encountered an IO error reading from the peer: {}", _0)] |
| PeerRead(#[cause] zx::Status), |
| |
| /// Encountered an IO error reading from the peer. |
| #[fail(display = "Encountered an IO error writing to the peer: {}", _0)] |
| PeerWrite(#[cause] zx::Status), |
| |
| /// A message couldn't be encoded. |
| #[fail(display = "Encontered an error encoding a message")] |
| Encoding, |
| |
| /// An error has been detected, and the request that is being handled |
| /// should be rejected with the error code given. |
| #[fail(display = "Invalid request detected: {:?}", _0)] |
| RequestInvalid(ErrorCode), |
| |
| /// Same as RequestInvalid, but an extra byte is included, which is used |
| /// in Stream and Configure responses |
| #[fail(display = "Invalid request detected: {:?} (extra: {:?})", _0, _1)] |
| RequestInvalidExtra(ErrorCode, u8), |
| |
| /// An operation was attempted in an Invalid State |
| #[fail(display = "Invalid State")] |
| InvalidState, |
| |
| #[doc(hidden)] |
| #[fail(display = "__Nonexhaustive error should never be created.")] |
| __Nonexhaustive, |
| } |
| |
| /// Generates an enum value where each variant can be converted into a constant in the given |
| /// raw_type. For example: |
| /// decodable_enum! { |
| /// Color<u8> { |
| /// Red => 1, |
| /// Blue => 2, |
| /// Green => 3, |
| /// } |
| /// } |
| /// Then Color::try_from(2) returns Color::Red, and u8::from(Color::Red) returns 1. |
| macro_rules! decodable_enum { |
| ($(#[$meta:meta])* $name:ident<$raw_type:ty> { |
| $($variant:ident => $val:expr),*, |
| }) => { |
| $(#[$meta])* |
| #[derive(Debug, PartialEq)] |
| pub(crate) enum $name { |
| $($variant),* |
| } |
| |
| tofrom_decodable_enum! { |
| $name<$raw_type> { |
| $($variant => $val),*, |
| } |
| } |
| } |
| } |
| |
| /// The same as decodable_enum, but the struct is public. |
| macro_rules! pub_decodable_enum { |
| ($(#[$meta:meta])* $name:ident<$raw_type:ty> { |
| $($variant:ident => $val:expr),*, |
| }) => { |
| $(#[$meta])* |
| #[derive(Debug, PartialEq)] |
| pub enum $name { |
| $($variant),* |
| } |
| |
| tofrom_decodable_enum! { |
| $name<$raw_type> { |
| $($variant => $val),*, |
| } |
| } |
| } |
| } |
| |
| /// A From<&$name> for $raw_type implementation and |
| /// TryFrom<$raw_type> for $name implementation, used by (pub_)decodable_enum |
| macro_rules! tofrom_decodable_enum { |
| ($name:ident<$raw_type:ty> { |
| $($variant:ident => $val:expr),*, |
| }) => { |
| impl From<&$name> for $raw_type { |
| fn from(v: &$name) -> $raw_type { |
| match v { |
| $($name::$variant => $val),*, |
| } |
| } |
| } |
| |
| impl TryFrom<$raw_type> for $name { |
| fn try_from(value: $raw_type) -> Result<Self> { |
| match value { |
| $($val => Ok($name::$variant)),*, |
| _ => Err(Error::OutOfRange), |
| } |
| } |
| } |
| } |
| } |
| |
| pub_decodable_enum! { |
| /// Error Codes that can be returned as part of a reject message. |
| /// See Section 8.20.6 |
| ErrorCode<u8> { |
| // Header Error Codes |
| BadHeaderFormat => 0x01, |
| |
| // Payload Format Error Codes |
| BadLength => 0x11, |
| BadAcpSeid => 0x12, |
| SepInUse => 0x13, |
| SepNotInUse => 0x14, |
| BadServiceCategory => 0x17, |
| BadPayloadFormat => 0x18, |
| NotSupportedCommand => 0x19, |
| InvalidCapabilities => 0x1A, |
| |
| // Transport Service Capabilities Error Codes |
| BadRecoveryType => 0x22, |
| BadMediaTransportFormat => 0x23, |
| BadRecoveryFormat => 0x25, |
| BadRohcFormat => 0x26, |
| BadCpFormat => 0x27, |
| BadMultiplexingFormat => 0x28, |
| UnsupportedConfiguration => 0x29, |
| |
| // Procedure Error Codes |
| BadState => 0x31, |
| } |
| } |
| |
| pub(crate) trait TryFrom<T>: Sized { |
| fn try_from(value: T) -> Result<Self>; |
| } |
| |
| /// A decodable type can be created from a byte buffer. |
| /// The type returned is separate (copied) from the buffer once decoded. |
| pub(crate) trait Decodable: Sized { |
| /// Decodes into a new object, or returns an error. |
| fn decode(buf: &[u8]) -> Result<Self>; |
| } |
| |
| /// A encodable type can write itself into a byte buffer. |
| pub(crate) trait Encodable: Sized { |
| /// Returns the number of bytes necessary to encode |self| |
| fn encoded_len(&self) -> usize; |
| |
| /// Writes the encoded version of |self| at the start of |buf| |
| /// |buf| must be at least size() length. |
| fn encode(&self, buf: &mut [u8]) -> Result<()>; |
| } |
| |
| /// An AVDTP Transaction Label |
| /// Not used outside the library. Public as part of some internal Error variants. |
| /// See Section 8.4.1 |
| #[derive(Debug, Clone, Copy, PartialEq)] |
| pub struct TxLabel(u8); |
| |
| // Transaction labels are only 4 bits. |
| const MAX_TX_LABEL: u8 = 0xF; |
| |
| impl TryFrom<u8> for TxLabel { |
| fn try_from(value: u8) -> Result<Self> { |
| if value > MAX_TX_LABEL { |
| fx_log_warn!("TxLabel out of range: {}", value); |
| Err(Error::OutOfRange) |
| } else { |
| Ok(TxLabel(value)) |
| } |
| } |
| } |
| |
| impl From<&TxLabel> for u8 { |
| fn from(v: &TxLabel) -> u8 { |
| v.0 |
| } |
| } |
| |
| impl From<&TxLabel> for usize { |
| fn from(v: &TxLabel) -> usize { |
| v.0 as usize |
| } |
| } |
| |
| pub_decodable_enum! { |
| /// Type of media |
| /// USed to specify the type of media on a stream endpoint. |
| /// Part of the StreamInformation in Discovery Response. |
| /// Defined in the Bluetooth Assigned Numbers |
| /// https://www.bluetooth.com/specifications/assigned-numbers/audio-video |
| #[derive(Clone)] |
| MediaType<u8> { |
| Audio => 0x00, |
| Video => 0x01, |
| Multimedia => 0x02, |
| } |
| } |
| |
| pub_decodable_enum! { |
| /// Type of endpoint (source or sync) |
| /// Part of the StreamInformation in Discovery Response. |
| /// See Section 8.20.3 |
| #[derive(Clone)] |
| EndpointType<u8> { |
| Source => 0x00, |
| Sink => 0x01, |
| } |
| } |
| |
| decodable_enum! { |
| /// Indicated whether this paket is part of a fragmented packet set. |
| /// See Section 8.4.2 |
| SignalingPacketType<u8> { |
| Single => 0x00, |
| Start => 0x01, |
| Continue => 0x02, |
| End => 0x03, |
| } |
| } |
| |
| decodable_enum! { |
| /// Specifies the command type of each signaling command or the response |
| /// type of each response packet. |
| /// See Section 8.4.3 |
| SignalingMessageType<u8> { |
| Command => 0x00, |
| GeneralReject => 0x01, |
| ResponseAccept => 0x02, |
| ResponseReject => 0x03, |
| } |
| } |
| |
| decodable_enum! { |
| /// Indicates the signaling command on a command packet. The same identifier is used on the |
| /// response to that command packet. |
| /// See Section 8.4.4 |
| #[derive(Copy, Clone)] |
| SignalIdentifier<u8> { |
| Discover => 0x01, |
| GetCapabilities => 0x02, |
| SetConfiguration => 0x03, |
| GetConfiguration => 0x04, |
| Reconfigure => 0x05, |
| Open => 0x06, |
| Start => 0x07, |
| Close => 0x08, |
| Suspend => 0x09, |
| Abort => 0x0A, |
| SecurityControl => 0x0B, |
| GetAllCapabilities => 0x0C, |
| DelayReport => 0x0D, |
| } |
| } |
| |
| #[derive(Debug)] |
| pub(crate) struct SignalingHeader { |
| pub label: TxLabel, |
| packet_type: SignalingPacketType, |
| message_type: SignalingMessageType, |
| num_packets: u8, |
| pub signal: SignalIdentifier, |
| } |
| |
| impl SignalingHeader { |
| pub fn new( |
| label: TxLabel, signal: SignalIdentifier, message_type: SignalingMessageType, |
| ) -> SignalingHeader { |
| SignalingHeader { |
| label: label, |
| signal: signal, |
| message_type: message_type, |
| packet_type: SignalingPacketType::Single, |
| num_packets: 1, |
| } |
| } |
| |
| pub fn label(&self) -> TxLabel { |
| self.label |
| } |
| |
| pub fn signal(&self) -> SignalIdentifier { |
| self.signal |
| } |
| |
| pub fn is_type(&self, other: SignalingMessageType) -> bool { |
| self.message_type == other |
| } |
| |
| pub fn is_command(&self) -> bool { |
| self.is_type(SignalingMessageType::Command) |
| } |
| } |
| |
| impl Decodable for SignalingHeader { |
| fn decode(bytes: &[u8]) -> Result<SignalingHeader> { |
| if bytes.len() < 2 { |
| return Err(Error::OutOfRange); |
| } |
| let label = TxLabel::try_from(bytes[0] >> 4)?; |
| let packet_type = SignalingPacketType::try_from((bytes[0] >> 2) & 0x3)?; |
| let (id_offset, num_packets) = match packet_type { |
| SignalingPacketType::Start => { |
| if bytes.len() < 3 { |
| return Err(Error::OutOfRange); |
| } |
| (2, bytes[1]) |
| } |
| _ => (1, 1), |
| }; |
| let signal_id_val = bytes[id_offset] & 0x3F; |
| let id = SignalIdentifier::try_from(signal_id_val) |
| .map_err(|_| Error::InvalidSignalId(label, signal_id_val))?; |
| let header = SignalingHeader { |
| label: label, |
| packet_type: packet_type, |
| message_type: SignalingMessageType::try_from(bytes[0] & 0x3)?, |
| signal: id, |
| num_packets: num_packets, |
| }; |
| Ok(header) |
| } |
| } |
| |
| impl Encodable for SignalingHeader { |
| fn encoded_len(&self) -> usize { |
| if self.num_packets > 1 { |
| 3 |
| } else { |
| 2 |
| } |
| } |
| |
| fn encode(&self, buf: &mut [u8]) -> Result<()> { |
| if buf.len() < self.encoded_len() { |
| return Err(Error::Encoding); |
| } |
| buf[0] = u8::from(&self.label) << 4 |
| | u8::from(&self.packet_type) << 2 |
| | u8::from(&self.message_type); |
| buf[1] = u8::from(&self.signal); |
| Ok(()) |
| } |
| } |
| |
| /// A Stream Endpoint Identifier, aka SEID, INT SEID, ACP SEID - Sec 8.20.1 |
| /// Valid values are 0x01 - 0x3E |
| #[derive(Debug, PartialEq, Eq, Clone, Hash)] |
| pub struct StreamEndpointId(pub(crate) u8); |
| |
| impl StreamEndpointId { |
| /// Interpret a StreamEndpointId from the upper six bits of a byte, which |
| /// is often how it's transmitted in a message. |
| pub(crate) fn from_msg(byte: &u8) -> Self { |
| StreamEndpointId(byte >> 2) |
| } |
| |
| /// Produce a byte where the SEID value is placed in the upper six bits, |
| /// which is often how it is placed in a mesage. |
| pub(crate) fn to_msg(&self) -> u8 { |
| self.0 << 2 |
| } |
| } |
| |
| impl TryFrom<u8> for StreamEndpointId { |
| fn try_from(value: u8) -> Result<Self> { |
| if value == 0 || value > 0x3E { |
| Err(Error::OutOfRange) |
| } else { |
| Ok(StreamEndpointId(value)) |
| } |
| } |
| } |
| |
| impl fmt::Display for StreamEndpointId { |
| fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |
| write!(fmt, "{}", self.0) |
| } |
| } |
| |
| /// The type of the codec in the MediaCodec Service Capability |
| /// Valid values are defined in the Bluetooth Assigned Numbers and are |
| /// interpreted differently for different Media Types, so we do not interpret |
| /// them here. |
| /// See https://www.bluetooth.com/specifications/assigned-numbers/audio-video |
| #[derive(Debug, PartialEq)] |
| pub struct MediaCodecType(u8); |
| |
| impl MediaCodecType { |
| pub fn new(num: u8) -> MediaCodecType { |
| MediaCodecType(num) |
| } |
| } |
| |
| /// The type of content protection used in the Content Protection Service Capability. |
| /// Defined in the Bluetooth Assigned Numbers |
| /// https://www.bluetooth.com/specifications/assigned-numbers/audio-video |
| #[derive(Debug, PartialEq)] |
| pub enum ContentProtectionType { |
| DigitalTransmissionContentProtection, // DTCP, 0x0001 |
| SerialCopyManagementSystem, // SCMS-T, 0x0002 |
| } |
| |
| impl ContentProtectionType { |
| fn to_le_bytes(&self) -> [u8; 2] { |
| match self { |
| ContentProtectionType::DigitalTransmissionContentProtection => [0x01, 0x00], |
| ContentProtectionType::SerialCopyManagementSystem => [0x02, 0x00], |
| } |
| } |
| } |
| |
| impl TryFrom<u16> for ContentProtectionType { |
| fn try_from(val: u16) -> Result<Self> { |
| match val { |
| 1 => Ok(ContentProtectionType::DigitalTransmissionContentProtection), |
| 2 => Ok(ContentProtectionType::SerialCopyManagementSystem), |
| _ => Err(Error::OutOfRange), |
| } |
| } |
| } |
| |
| pub_decodable_enum! { |
| /// Indicates the signaling command on a command packet. The same identifier is used on the |
| /// response to that command packet. |
| /// See Section 8.4.4 |
| ServiceCategory<u8> { |
| None => 0x00, |
| MediaTransport => 0x01, |
| Reporting => 0x02, |
| Recovery => 0x03, |
| ContentProtection => 0x04, |
| HeaderCompression => 0x05, |
| Multiplexing => 0x06, |
| MediaCodec => 0x07, |
| DelayReporting => 0x08, |
| } |
| } |
| /// Service Capabilities indicate possible services that can be provided by |
| /// each stream endpoint. See AVDTP Spec section 8.21. |
| #[derive(Debug, PartialEq)] |
| pub enum ServiceCapability { |
| /// Indicates that the end point can provide at least basic media transport |
| /// service as defined by RFC 3550 and outlined in section 7.2. |
| /// Defined in section 8.21.2 |
| MediaTransport, |
| /// Indicates that the end point can provide reporting service as outlined in section 7.3 |
| /// Defined in section 8.21.3 |
| Reporting, |
| /// Indicates the end point can provide recovery service as outlined in section 7.4 |
| /// Defined in section 8.21.4 |
| Recovery { |
| recovery_type: u8, |
| max_recovery_window_size: u8, |
| max_number_media_packets: u8, |
| }, |
| /// Indicates the codec which is supported by this end point. |codec_extra| is defined within |
| /// the relevant profiles (A2DP for Audio, etc). |
| /// Defined in section 8.21.5 |
| MediaCodec { |
| media_type: MediaType, |
| codec_type: MediaCodecType, |
| codec_extra: Vec<u8>, // TODO: Media codec specific information elements |
| }, |
| /// Present when the device has content protection capability. |
| /// |extra| is defined elsewhere. |
| /// Defined in section 8.21.6 |
| ContentProtection { |
| protection_type: ContentProtectionType, |
| extra: Vec<u8>, // Protection speciifc parameters |
| }, |
| /// Indicates that delay reporting is offered by this end point. |
| /// Defined in section 8.21.9 |
| DelayReporting, |
| } |
| |
| impl ServiceCapability { |
| pub(crate) fn to_category_byte(&self) -> u8 { |
| match self { |
| ServiceCapability::MediaTransport => &ServiceCategory::MediaTransport, |
| ServiceCapability::Reporting => &ServiceCategory::Reporting, |
| ServiceCapability::Recovery { .. } => &ServiceCategory::Recovery, |
| ServiceCapability::ContentProtection { .. } => &ServiceCategory::ContentProtection, |
| ServiceCapability::MediaCodec { .. } => &ServiceCategory::MediaCodec, |
| ServiceCapability::DelayReporting => &ServiceCategory::DelayReporting, |
| } |
| .into() |
| } |
| |
| pub(crate) fn length_of_service_capabilities(&self) -> u8 { |
| match self { |
| ServiceCapability::MediaTransport => 0, |
| ServiceCapability::Reporting => 0, |
| ServiceCapability::Recovery { .. } => 3, |
| ServiceCapability::MediaCodec { codec_extra, .. } => 2 + codec_extra.len() as u8, |
| ServiceCapability::ContentProtection { extra, .. } => 2 + extra.len() as u8, |
| ServiceCapability::DelayReporting => 0, |
| } |
| } |
| |
| /// True when this ServiceCapability is a "basic" capability. |
| /// See Table 8.47 in Section 8.21.1 |
| pub(crate) fn is_basic(&self) -> bool { |
| match self { |
| ServiceCapability::DelayReporting => false, |
| _ => true, |
| } |
| } |
| |
| /// True when this capability should be included in the response to a |sig| command. |
| pub(crate) fn in_response(&self, sig: SignalIdentifier) -> bool { |
| sig == SignalIdentifier::GetAllCapabilities || self.is_basic() |
| } |
| |
| /// True when this capability is classified as an "Application Service Capability" |
| pub(crate) fn is_application(&self) -> bool { |
| match self { |
| ServiceCapability::MediaCodec { .. } | ServiceCapability::ContentProtection { .. } => { |
| true |
| } |
| _ => false, |
| } |
| } |
| } |
| |
| impl Decodable for ServiceCapability { |
| fn decode(from: &[u8]) -> Result<Self> { |
| if from.len() < 2 { |
| return Err(Error::Encoding); |
| } |
| let length_of_capability = from[1] as usize; |
| let d = match ServiceCategory::try_from(from[0]) { |
| Ok(ServiceCategory::MediaTransport) => match length_of_capability { |
| 0 => ServiceCapability::MediaTransport, |
| _ => return Err(Error::RequestInvalid(ErrorCode::BadMediaTransportFormat)), |
| }, |
| Ok(ServiceCategory::Reporting) => match length_of_capability { |
| 0 => ServiceCapability::Reporting, |
| _ => return Err(Error::RequestInvalid(ErrorCode::BadPayloadFormat)), |
| }, |
| Ok(ServiceCategory::Recovery) => { |
| if from.len() < 5 || length_of_capability != 3 { |
| return Err(Error::RequestInvalid(ErrorCode::BadRecoveryFormat)); |
| } |
| let recovery_type = from[2]; |
| let mrws = from[3]; |
| let mnmp = from[4]; |
| // Check format of parameters. See Section 8.21.4, Table 8.51 |
| // The only recovery type is RFC2733 (0x01) |
| if recovery_type != 0x01 { |
| return Err(Error::RequestInvalid(ErrorCode::BadRecoveryType)); |
| } |
| // The MRWS and MNMP must be 0x01 - 0x18 |
| if mrws < 0x01 || mrws > 0x18 || mnmp < 0x01 || mnmp > 0x18 { |
| return Err(Error::RequestInvalid(ErrorCode::BadRecoveryFormat)); |
| } |
| ServiceCapability::Recovery { |
| recovery_type, |
| max_recovery_window_size: mrws, |
| max_number_media_packets: mnmp, |
| } |
| } |
| Ok(ServiceCategory::ContentProtection) => { |
| let cp_format_err = Err(Error::RequestInvalid(ErrorCode::BadCpFormat)); |
| if from.len() < 4 |
| || length_of_capability < 2 |
| || from.len() < length_of_capability + 2 |
| { |
| return cp_format_err; |
| } |
| let cp_type: u16 = ((from[3] as u16) << 8) + from[2] as u16; |
| let protection_type = match ContentProtectionType::try_from(cp_type) { |
| Ok(val) => val, |
| Err(_) => return cp_format_err, |
| }; |
| let extra_len = length_of_capability - 2; |
| let mut extra = vec![0; extra_len]; |
| extra.copy_from_slice(&from[4..4 + extra_len]); |
| ServiceCapability::ContentProtection { |
| protection_type, |
| extra, |
| } |
| } |
| Ok(ServiceCategory::MediaCodec) => { |
| let format_err = Err(Error::RequestInvalid(ErrorCode::BadPayloadFormat)); |
| if from.len() < 4 |
| || length_of_capability < 2 |
| || from.len() < length_of_capability + 2 |
| { |
| return format_err; |
| } |
| let media_type = match MediaType::try_from(from[2] >> 4) { |
| Ok(media) => media, |
| Err(_) => return format_err, |
| }; |
| let codec_type = MediaCodecType::new(from[3]); |
| let extra_len = length_of_capability - 2; |
| let mut codec_extra = vec![0; extra_len]; |
| codec_extra.copy_from_slice(&from[4..4 + extra_len]); |
| ServiceCapability::MediaCodec { |
| media_type, |
| codec_type, |
| codec_extra, |
| } |
| } |
| Ok(ServiceCategory::DelayReporting) => match length_of_capability { |
| 0 => ServiceCapability::DelayReporting, |
| _ => return Err(Error::RequestInvalid(ErrorCode::BadPayloadFormat)), |
| }, |
| _ => { |
| return Err(Error::RequestInvalid(ErrorCode::BadServiceCategory)); |
| } |
| }; |
| Ok(d) |
| } |
| } |
| |
| impl Encodable for ServiceCapability { |
| fn encoded_len(&self) -> usize { |
| 2 + self.length_of_service_capabilities() as usize |
| } |
| |
| fn encode(&self, buf: &mut [u8]) -> Result<()> { |
| if buf.len() < self.encoded_len() { |
| return Err(Error::Encoding); |
| } |
| let mut cursor = Cursor::new(buf); |
| cursor |
| .write(&[ |
| self.to_category_byte(), |
| self.length_of_service_capabilities(), |
| ]) |
| .map_err(|_| Error::Encoding)?; |
| match self { |
| ServiceCapability::Recovery { |
| recovery_type: t, |
| max_recovery_window_size: max_size, |
| max_number_media_packets: max_packets, |
| } => { |
| cursor |
| .write(&[*t, *max_size, *max_packets]) |
| .map_err(|_| Error::Encoding)?; |
| } |
| ServiceCapability::MediaCodec { |
| media_type, |
| codec_type, |
| codec_extra, |
| } => { |
| cursor |
| .write(&[u8::from(media_type) << 4, codec_type.0]) |
| .map_err(|_| Error::Encoding)?; |
| cursor |
| .write(codec_extra.as_slice()) |
| .map_err(|_| Error::Encoding)?; |
| } |
| ServiceCapability::ContentProtection { |
| protection_type, |
| extra, |
| } => { |
| cursor |
| .write(&protection_type.to_le_bytes()) |
| .map_err(|_| Error::Encoding)?; |
| cursor |
| .write(extra.as_slice()) |
| .map_err(|_| Error::Encoding)?; |
| } |
| _ => {} |
| } |
| Ok(()) |
| } |
| } |
| |
| /// All information related to a stream. Part of the Discovery Response. |
| /// See Sec 8.6.2 |
| #[derive(Debug, PartialEq)] |
| pub struct StreamInformation { |
| id: StreamEndpointId, |
| in_use: bool, |
| media_type: MediaType, |
| endpoint_type: EndpointType, |
| } |
| |
| impl StreamInformation { |
| /// Create a new StreamInformation from an ID. |
| /// This will only fail if the ID given is out of the range of valid SEIDs (0x01 - 0x3E) |
| pub fn new( |
| id: StreamEndpointId, in_use: bool, media_type: MediaType, endpoint_type: EndpointType, |
| ) -> StreamInformation { |
| StreamInformation { |
| id: id, |
| in_use: in_use, |
| media_type: media_type, |
| endpoint_type: endpoint_type, |
| } |
| } |
| |
| pub fn id(&self) -> &StreamEndpointId { |
| &self.id |
| } |
| |
| pub fn in_use(&self) -> &bool { |
| &self.in_use |
| } |
| } |
| |
| impl Decodable for StreamInformation { |
| fn decode(from: &[u8]) -> Result<Self> { |
| if from.len() < 2 { |
| return Err(Error::InvalidMessage); |
| } |
| let id = StreamEndpointId::from_msg(&from[0]); |
| let in_use: bool = from[0] & 0x02 != 0; |
| let media_type = MediaType::try_from(from[1] >> 4)?; |
| let endpoint_type = EndpointType::try_from((from[1] >> 3) & 0x1)?; |
| Ok(StreamInformation { |
| id: id, |
| in_use: in_use, |
| media_type: media_type, |
| endpoint_type: endpoint_type, |
| }) |
| } |
| } |
| |
| impl Encodable for StreamInformation { |
| fn encoded_len(&self) -> usize { |
| 2 |
| } |
| |
| fn encode(&self, into: &mut [u8]) -> Result<()> { |
| if into.len() < self.encoded_len() { |
| return Err(Error::Encoding); |
| } |
| into[0] = self.id.to_msg() | if self.in_use { 0x02 } else { 0x00 }; |
| into[1] = u8::from(&self.media_type) << 4 | u8::from(&self.endpoint_type) << 3; |
| Ok(()) |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| |
| decodable_enum! { |
| TestEnum<u16> { |
| One => 1, |
| Two => 2, |
| Max => 65535, |
| } |
| } |
| |
| #[test] |
| fn try_from_success() { |
| let one = TestEnum::try_from(1); |
| assert!(one.is_ok()); |
| assert_eq!(TestEnum::One, one.unwrap()); |
| let two = TestEnum::try_from(2); |
| assert!(two.is_ok()); |
| assert_eq!(TestEnum::Two, two.unwrap()); |
| let max = TestEnum::try_from(65535); |
| assert!(max.is_ok()); |
| assert_eq!(TestEnum::Max, max.unwrap()); |
| } |
| |
| #[test] |
| fn try_from_error() { |
| let err = TestEnum::try_from(5); |
| assert_eq!(Some(Error::OutOfRange), err.err()); |
| } |
| |
| #[test] |
| fn into_rawtype() { |
| let raw = u16::from(&TestEnum::One); |
| assert_eq!(1, raw); |
| let raw = u16::from(&TestEnum::Two); |
| assert_eq!(2, raw); |
| let raw = u16::from(&TestEnum::Max); |
| assert_eq!(65535, raw); |
| } |
| |
| #[test] |
| fn txlabel_tofrom_u8() { |
| let mut label: Result<TxLabel> = TxLabel::try_from(15); |
| assert!(label.is_ok()); |
| assert_eq!(15, u8::from(&label.unwrap())); |
| label = TxLabel::try_from(16); |
| assert_eq!(Err(Error::OutOfRange), label); |
| } |
| |
| #[test] |
| fn txlabel_to_usize() { |
| let label = TxLabel::try_from(1).unwrap(); |
| assert_eq!(1, usize::from(&label)); |
| } |
| } |