| // 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 { |
| fuchsia_zircon as zx, |
| packet_encoding::{decodable_enum, Decodable, Encodable}, |
| std::{ |
| fmt, |
| io::{Cursor, Write}, |
| result, |
| }, |
| thiserror::Error, |
| }; |
| |
| /// Result type for AVDTP, using avdtp::Error |
| pub type Result<T> = result::Result<T, Error>; |
| |
| /// The error type of the AVDTP library. |
| #[derive(Error, Debug)] |
| #[non_exhaustive] |
| pub enum Error { |
| /// The value that was sent on the wire was out of range. |
| #[error("Value was out of range")] |
| OutOfRange, |
| |
| /// The signal identifier was invalid when parsing a message. |
| #[error("Invalid signal id for {:?}: {:X?}", _0, _1)] |
| InvalidSignalId(TxLabel, u8), |
| |
| /// The header was invalid when parsing a message from the peer. |
| #[error("Invalid Header for a AVDTP message")] |
| InvalidHeader, |
| |
| /// The body format was invalid when parsing a message from the peer. |
| #[error("Failed to parse AVDTP message contents")] |
| InvalidMessage, |
| |
| /// The remote end failed to respond to this command in time. |
| #[error("Command timed out")] |
| Timeout, |
| |
| /// The Remote end rejected a command we sent (with this error code) |
| #[error("Remote end rejected the command ({:})", _0)] |
| RemoteRejected(#[from] RemoteReject), |
| |
| /// When a message hasn't been implemented yet, the parser will return this. |
| #[error("Message has not been implemented yet")] |
| UnimplementedMessage, |
| |
| /// The distant peer has disconnected. |
| #[error("Peer has disconnected")] |
| PeerDisconnected, |
| |
| /// Sent if a Command Future is polled after it's already completed |
| #[error("Command Response has already been received")] |
| AlreadyReceived, |
| |
| /// Encountered an IO error setting up the channel |
| #[error("Encountered an IO error reading from the peer: {}", _0)] |
| ChannelSetup(zx::Status), |
| |
| /// Encountered an IO error reading from the peer. |
| #[error("Encountered an IO error reading from the peer: {}", _0)] |
| PeerRead(zx::Status), |
| |
| /// Encountered an IO error reading from the peer. |
| #[error("Encountered an IO error writing to the peer: {}", _0)] |
| PeerWrite(zx::Status), |
| |
| /// A message couldn't be encoded. |
| #[error("Encountered 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. |
| #[error("Invalid request detected: {:?}", _0)] |
| RequestInvalid(ErrorCode), |
| |
| /// Same as RequestInvalid, but an extra byte is included, which is used |
| /// in Stream and Configure responses |
| #[error("Invalid request detected: {:?} (extra: {:?})", _0, _1)] |
| RequestInvalidExtra(ErrorCode, u8), |
| |
| /// An operation was attempted in an Invalid State |
| #[error("Invalid State")] |
| InvalidState, |
| |
| /// An error from another source |
| #[error(transparent)] |
| Other(#[from] anyhow::Error), |
| } |
| |
| /// Errors that can be returned by the remote peer in response to a message |
| #[derive(Error, Debug)] |
| #[cfg_attr(test, derive(PartialEq))] |
| pub struct RemoteReject { |
| /// The signal identifier in this rejection. |
| /// This is the only field populated for a General Rejection, which only occurs when the remote |
| /// end does not understand the SignalIdentifier (but we do, or we would return a |
| /// `Error::InvalidHeader`) |
| signal_id: SignalIdentifier, |
| /// Error code reported for this error. |
| error_code: Option<u8>, |
| /// The service category reported that applies to this error. Only set when `signal_id` is |
| /// SetConfiguration or Reconfigure. |
| service_category: Option<u8>, |
| /// The StreamEndpointId reported that applies for this rejection. |
| stream_endpoint_id: Option<StreamEndpointId>, |
| } |
| |
| impl RemoteReject { |
| pub(crate) fn from_params(signal_id: SignalIdentifier, params: &[u8]) -> Self { |
| if params.len() == 0 { |
| // General Reject |
| return Self { |
| signal_id, |
| error_code: None, |
| service_category: None, |
| stream_endpoint_id: None, |
| }; |
| } |
| let (error_code, service_category, stream_endpoint_id) = match signal_id { |
| SignalIdentifier::SetConfiguration | SignalIdentifier::Reconfigure => { |
| (params.get(1).copied(), params.get(0).copied(), None) |
| } |
| SignalIdentifier::Start | SignalIdentifier::Suspend => { |
| // The Stream ID here is in the top 6 bits, with the bottom 2 bits RFA. |
| (params.get(1).copied(), None, params.get(0).map(StreamEndpointId::from_msg)) |
| } |
| _ => (params.get(0).copied(), None, None), |
| }; |
| Self { signal_id, error_code, service_category, stream_endpoint_id } |
| } |
| |
| /// Retrieve the error code returned by the remote end. |
| /// Returns Some(Err(u8)) if the code isn't recognized, or None if it wasn't included |
| pub fn error_code(&self) -> Option<std::result::Result<ErrorCode, u8>> { |
| self.error_code.map(|e| ErrorCode::try_from(e).map_err(|_| e)) |
| } |
| |
| /// Retrieve the ServiceCategory returned by the remote end. |
| /// Returns Err(u8) if the category wasn't recognized, or None if it wasn't included |
| pub fn service_category(&self) -> Option<std::result::Result<ServiceCategory, u8>> { |
| self.service_category.map(|e| ServiceCategory::try_from(e).map_err(|_| e)) |
| } |
| |
| /// Retrieve the stream identifier returned from the remote end. |
| /// Returns None if it wasn't included. |
| pub fn stream_id(&self) -> Option<StreamEndpointId> { |
| self.stream_endpoint_id.clone() |
| } |
| |
| #[cfg(test)] |
| pub fn general(signal_id: SignalIdentifier) -> Self { |
| Self { signal_id, error_code: None, service_category: None, stream_endpoint_id: None } |
| } |
| |
| #[cfg(test)] |
| pub fn rejected(signal_id: SignalIdentifier, code: u8) -> Self { |
| Self { signal_id, error_code: Some(code), service_category: None, stream_endpoint_id: None } |
| } |
| |
| #[cfg(test)] |
| pub fn config(signal_id: SignalIdentifier, cat: u8, code: u8) -> Self { |
| Self { |
| signal_id, |
| error_code: Some(code), |
| service_category: Some(cat), |
| stream_endpoint_id: None, |
| } |
| } |
| |
| #[cfg(test)] |
| pub fn stream(signal_id: SignalIdentifier, stream: u8, code: u8) -> Self { |
| Self { |
| signal_id, |
| error_code: Some(code), |
| service_category: None, |
| stream_endpoint_id: stream.try_into().ok(), |
| } |
| } |
| } |
| |
| impl std::fmt::Display for RemoteReject { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { |
| let Some(error_code) = self.error_code else { |
| return write!(f, "did not recognize {:?}", self.signal_id); |
| }; |
| |
| write!(f, "{:?}, ", self.signal_id)?; |
| match self.signal_id { |
| SignalIdentifier::SetConfiguration | SignalIdentifier::Reconfigure => { |
| write!(f, "category {:?}, ", self.service_category)?; |
| } |
| SignalIdentifier::Start | SignalIdentifier::Suspend => { |
| write!(f, "stream id {:?}, ", self.stream_endpoint_id)?; |
| } |
| _ => {} |
| }; |
| write!(f, "{:x}", error_code) |
| } |
| } |
| |
| decodable_enum! { |
| /// Error Codes that can be returned as part of a reject message. |
| /// See Section 8.20.6 |
| pub enum ErrorCode<u8, Error, OutOfRange> { |
| // 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, |
| } |
| } |
| |
| /// 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 { |
| type Error = Error; |
| |
| fn try_from(value: u8) -> Result<Self> { |
| if value > MAX_TX_LABEL { |
| 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 |
| } |
| } |
| |
| 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 |
| pub enum MediaType<u8, Error, OutOfRange> { |
| Audio = 0x00, |
| Video = 0x01, |
| Multimedia = 0x02, |
| } |
| } |
| |
| decodable_enum! { |
| /// Type of endpoint (source or sync) |
| /// Part of the StreamInformation in Discovery Response. |
| /// See Section 8.20.3 |
| pub enum EndpointType<u8, Error, OutOfRange> { |
| Source = 0x00, |
| Sink = 0x01, |
| } |
| } |
| |
| impl EndpointType { |
| pub fn opposite(&self) -> Self { |
| match self { |
| Self::Source => Self::Sink, |
| Self::Sink => Self::Source, |
| } |
| } |
| } |
| |
| decodable_enum! { |
| /// Indicated whether this packet is part of a fragmented packet set. |
| /// See Section 8.4.2 |
| pub(crate) enum SignalingPacketType<u8, Error, OutOfRange> { |
| 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 |
| pub(crate) enum SignalingMessageType<u8, Error, OutOfRange> { |
| 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 |
| pub enum SignalIdentifier<u8, Error, OutOfRange> { |
| 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, |
| pub(crate) message_type: SignalingMessageType, |
| num_packets: u8, |
| pub signal: SignalIdentifier, |
| } |
| |
| impl SignalingHeader { |
| pub fn new( |
| label: TxLabel, |
| signal: SignalIdentifier, |
| message_type: SignalingMessageType, |
| ) -> SignalingHeader { |
| SignalingHeader { |
| label, |
| signal, |
| 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 { |
| type Error = Error; |
| |
| 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, |
| packet_type, |
| message_type: SignalingMessageType::try_from(bytes[0] & 0x3)?, |
| signal: id, |
| num_packets, |
| }; |
| Ok(header) |
| } |
| } |
| |
| impl Encodable for SignalingHeader { |
| type Error = Error; |
| |
| 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 message. |
| pub(crate) fn to_msg(&self) -> u8 { |
| self.0 << 2 |
| } |
| } |
| |
| impl TryFrom<u8> for StreamEndpointId { |
| type Error = Error; |
| |
| 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. |
| /// Associated constants are provided that specify the value of `MediaCodecType` |
| /// for different codecs given the `MediaType::Audio`. |
| /// See https://www.bluetooth.com/specifications/assigned-numbers/audio-video |
| #[derive(PartialEq, Eq, Clone)] |
| pub struct MediaCodecType(u8); |
| |
| impl MediaCodecType { |
| pub const AUDIO_SBC: Self = MediaCodecType(0b0); |
| pub const AUDIO_MPEG12: Self = MediaCodecType(0b1); |
| pub const AUDIO_AAC: Self = MediaCodecType(0b10); |
| pub const AUDIO_ATRAC: Self = MediaCodecType(0b100); |
| pub const AUDIO_NON_A2DP: Self = MediaCodecType(0b1111_1111); |
| |
| pub fn new(num: u8) -> MediaCodecType { |
| MediaCodecType(num) |
| } |
| } |
| |
| impl fmt::Debug for MediaCodecType { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| match self { |
| &Self::AUDIO_SBC => write!(f, "MediaCodecType::AUDIO_SBC"), |
| &Self::AUDIO_MPEG12 => write!(f, "MediaCodecType::AUDIO_MPEG12"), |
| &Self::AUDIO_AAC => write!(f, "MediaCodecType::AUDIO_AAC"), |
| &Self::AUDIO_ATRAC => write!(f, "MediaCodecType::AUDIO_ATRAC"), |
| &Self::AUDIO_NON_A2DP => write!(f, "MediaCodecType::AUDIO_NON_A2DP"), |
| _ => f.debug_tuple("MediaCodecType").field(&self.0).finish(), |
| } |
| } |
| } |
| |
| /// 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, Clone)] |
| 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 { |
| type Error = Error; |
| |
| fn try_from(val: u16) -> Result<Self> { |
| match val { |
| 1 => Ok(ContentProtectionType::DigitalTransmissionContentProtection), |
| 2 => Ok(ContentProtectionType::SerialCopyManagementSystem), |
| _ => Err(Error::OutOfRange), |
| } |
| } |
| } |
| |
| 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(PartialOrd, Ord)] |
| pub enum ServiceCategory<u8, Error, OutOfRange> { |
| 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, Clone)] |
| 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 specific parameters |
| }, |
| /// Indicates that header compression capabilities is offered by this end point. |
| /// Defined in section 8.21.7 |
| /// TODO(https://fxbug.dev/42114013): Implement header compression specific fields to use the payload. |
| HeaderCompression { payload_len: u8 }, |
| /// Indicates that multiplexing service is offered by this end point. |
| /// Defined in section 8.21.8 |
| /// TODO(https://fxbug.dev/42114015): Implement multiplexing specific fields to use the payload. |
| Multiplexing { payload_len: u8 }, |
| /// Indicates that delay reporting is offered by this end point. |
| /// Defined in section 8.21.9 |
| DelayReporting, |
| } |
| |
| impl ServiceCapability { |
| pub fn category(&self) -> ServiceCategory { |
| 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, |
| ServiceCapability::HeaderCompression { .. } => ServiceCategory::HeaderCompression, |
| ServiceCapability::Multiplexing { .. } => ServiceCategory::Multiplexing, |
| } |
| } |
| |
| 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, |
| ServiceCapability::HeaderCompression { payload_len } => *payload_len, |
| ServiceCapability::Multiplexing { payload_len } => *payload_len, |
| } |
| } |
| |
| /// 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::GetCapabilities || 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, |
| } |
| } |
| |
| pub fn is_codec(&self) -> bool { |
| match self { |
| ServiceCapability::MediaCodec { .. } => true, |
| _ => false, |
| } |
| } |
| |
| pub fn codec_type(&self) -> Option<&MediaCodecType> { |
| match self { |
| ServiceCapability::MediaCodec { codec_type, .. } => Some(codec_type), |
| _ => None, |
| } |
| } |
| } |
| |
| impl Decodable for ServiceCapability { |
| type Error = Error; |
| |
| 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_err = Err(Error::RequestInvalid(ErrorCode::BadCpFormat)); |
| if from.len() < 4 |
| || length_of_capability < 2 |
| || from.len() < length_of_capability + 2 |
| { |
| return cp_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_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 err = Err(Error::RequestInvalid(ErrorCode::BadPayloadFormat)); |
| if from.len() < 4 |
| || length_of_capability < 2 |
| || from.len() < length_of_capability + 2 |
| { |
| return err; |
| } |
| let media_type = match MediaType::try_from(from[2] >> 4) { |
| Ok(media) => media, |
| Err(_) => return 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)), |
| }, |
| Ok(ServiceCategory::Multiplexing) => { |
| ServiceCapability::Multiplexing { payload_len: length_of_capability as u8 } |
| } |
| Ok(ServiceCategory::HeaderCompression) => { |
| ServiceCapability::HeaderCompression { payload_len: length_of_capability as u8 } |
| } |
| _ => { |
| return Err(Error::RequestInvalid(ErrorCode::BadServiceCategory)); |
| } |
| }; |
| Ok(d) |
| } |
| } |
| |
| impl Encodable for ServiceCapability { |
| type Error = Error; |
| |
| 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); |
| let _ = cursor |
| .write(&[u8::from(&self.category()), 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, |
| } => { |
| let _ = |
| cursor.write(&[*t, *max_size, *max_packets]).map_err(|_| Error::Encoding)?; |
| } |
| ServiceCapability::MediaCodec { media_type, codec_type, codec_extra } => { |
| let _ = cursor |
| .write(&[u8::from(media_type) << 4, codec_type.0]) |
| .map_err(|_| Error::Encoding)?; |
| let _ = cursor.write(codec_extra.as_slice()).map_err(|_| Error::Encoding)?; |
| } |
| ServiceCapability::ContentProtection { protection_type, extra } => { |
| let _ = |
| cursor.write(&protection_type.to_le_bytes()).map_err(|_| Error::Encoding)?; |
| let _ = 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, in_use, media_type, endpoint_type } |
| } |
| |
| pub fn id(&self) -> &StreamEndpointId { |
| &self.id |
| } |
| |
| pub fn media_type(&self) -> &MediaType { |
| &self.media_type |
| } |
| |
| pub fn endpoint_type(&self) -> &EndpointType { |
| &self.endpoint_type |
| } |
| |
| pub fn in_use(&self) -> &bool { |
| &self.in_use |
| } |
| } |
| |
| impl Decodable for StreamInformation { |
| type Error = Error; |
| |
| 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, in_use, media_type, endpoint_type }) |
| } |
| } |
| |
| impl Encodable for StreamInformation { |
| type Error = Error; |
| |
| 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::*; |
| use assert_matches::assert_matches; |
| |
| #[test] |
| fn remote_reject_from_params_short() { |
| let r = RemoteReject::from_params(SignalIdentifier::Discover, &[]); |
| assert_eq!(None, r.error_code()); |
| assert_eq!(None, r.service_category()); |
| assert_eq!(None, r.stream_id()); |
| |
| let r = RemoteReject::from_params(SignalIdentifier::Start, &[0x12]); |
| assert_eq!(None, r.error_code()); |
| assert_eq!(None, r.service_category()); |
| assert_eq!(Some(StreamEndpointId(4)), r.stream_id()); |
| |
| let r = RemoteReject::from_params(SignalIdentifier::SetConfiguration, &[0x02]); |
| assert_eq!(None, r.error_code()); |
| assert_eq!(Some(Ok(ServiceCategory::Reporting)), r.service_category()); |
| assert_eq!(None, r.stream_id()); |
| } |
| |
| #[test] |
| fn remote_reject_ignores_unexpected_params() { |
| let r: RemoteReject = |
| RemoteReject::from_params(SignalIdentifier::Open, &[0x01, 0x02, 0x03, 0x04]); |
| assert_eq!(Some(Ok(ErrorCode::BadHeaderFormat)), r.error_code()); |
| assert_eq!(None, r.service_category()); |
| assert_eq!(None, r.stream_id()); |
| |
| let r: RemoteReject = RemoteReject::from_params( |
| SignalIdentifier::SetConfiguration, |
| &[0x02, 0x12, 0x03, 0x04], |
| ); |
| assert_eq!(Some(Ok(ErrorCode::BadAcpSeid)), r.error_code()); |
| assert_eq!(Some(Ok(ServiceCategory::Reporting)), r.service_category()); |
| assert_eq!(None, r.stream_id()); |
| |
| let r: RemoteReject = |
| RemoteReject::from_params(SignalIdentifier::Start, &[0x04, 0x01, 0x03, 0x04]); |
| assert_eq!(Some(Ok(ErrorCode::BadHeaderFormat)), r.error_code()); |
| assert_eq!(None, r.service_category()); |
| assert_eq!(Some(StreamEndpointId(1)), r.stream_id()); |
| } |
| |
| #[test] |
| fn remote_reject_unrecognized_category() { |
| let r: RemoteReject = |
| RemoteReject::from_params(SignalIdentifier::SetConfiguration, &[0xF0, 0x01]); |
| assert_eq!(Some(Ok(ErrorCode::BadHeaderFormat)), r.error_code()); |
| assert_eq!(Some(Err(0xF0)), r.service_category()); |
| assert_eq!(None, r.stream_id()); |
| } |
| |
| #[test] |
| fn remote_reject_unrecognized_errorcode() { |
| let r: RemoteReject = RemoteReject::from_params(SignalIdentifier::Discover, &[0xF1]); |
| assert_eq!(Some(Err(0xF1)), r.error_code()); |
| assert_eq!(None, r.service_category()); |
| assert_eq!(None, r.stream_id()); |
| } |
| |
| #[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_matches!(label, Err(Error::OutOfRange)); |
| } |
| |
| #[test] |
| fn txlabel_to_usize() { |
| let label = TxLabel::try_from(1).unwrap(); |
| assert_eq!(1, usize::from(&label)); |
| } |
| |
| #[test] |
| fn test_capability_inspection() { |
| let sbc_codec = ServiceCapability::MediaCodec { |
| media_type: MediaType::Audio, |
| codec_type: MediaCodecType::AUDIO_SBC.clone(), |
| codec_extra: vec![0x01], |
| }; |
| let transport = ServiceCapability::MediaTransport; |
| |
| assert!(sbc_codec.is_application()); |
| assert!(!transport.is_application()); |
| |
| assert!(sbc_codec.is_codec()); |
| assert!(!transport.is_codec()); |
| |
| assert_eq!(Some(&MediaCodecType::AUDIO_SBC), sbc_codec.codec_type()); |
| assert_eq!(None, transport.codec_type()); |
| } |
| } |