| // 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. |
| |
| #include "mux_commands.h" |
| |
| #include <cstring> |
| |
| #include "garnet/drivers/bluetooth/lib/common/log.h" |
| |
| namespace btlib { |
| namespace rfcomm { |
| |
| namespace { |
| |
| // Used to mask different parts of the type and length fields. See GSM 5.4.6.1. |
| constexpr uint8_t kEAMask = 0b00000001; |
| constexpr uint8_t kCRMask = 0b00000010; |
| constexpr uint8_t kTypeMask = 0b11111100; |
| |
| constexpr size_t kTypeIndex = 0; |
| constexpr size_t kLengthIndex = 1; |
| |
| constexpr size_t kLengthShift = 1; |
| |
| // The information lengths for different types of mux commands. These are the |
| // only values that should appear in the length field for these commands. |
| constexpr size_t kPNLength = 8; |
| constexpr size_t kMSCWithoutBreakLength = 2; |
| constexpr size_t kMSCWithBreakLength = 3; |
| constexpr size_t kNSCLength = 1; |
| constexpr size_t kFConLength = 0; |
| constexpr size_t kFCoffLength = 0; |
| constexpr size_t kRPNShortLength = 1; |
| constexpr size_t kRPNLongLength = 8; |
| constexpr size_t kRLSLength = 2; |
| |
| constexpr size_t kPNCreditBasedFlowHandshakeShift = 4; |
| |
| constexpr uint8_t kMSCDLCIShift = 2; |
| constexpr size_t kMSCFlowControlShift = 1; |
| constexpr uint8_t kMSCFlowControlMask = 1 << kMSCFlowControlShift; |
| constexpr size_t kMSCReadyToCommunicateShift = 2; |
| constexpr uint8_t kMSCReadyToCommunicateMask = 1 << kMSCReadyToCommunicateShift; |
| constexpr size_t kMSCReadyToReceiveShift = 3; |
| constexpr uint8_t kMSCReadyToReceiveMask = 1 << kMSCReadyToReceiveShift; |
| constexpr size_t kMSCIncomingCallShift = 6; |
| constexpr uint8_t kMSCIncomingCallMask = 1 << kMSCIncomingCallShift; |
| constexpr size_t kMSCDataValidShift = 7; |
| constexpr uint8_t kMSCDataValidMask = 1 << kMSCDataValidShift; |
| constexpr size_t kMSCBreakSignalShift = 1; |
| constexpr uint8_t kMSCBreakSignalMask = 1 << kMSCBreakSignalShift; |
| constexpr size_t kMSCBreakValueShift = 4; |
| |
| constexpr size_t kNSCNotSupportedCommandShift = 2; |
| constexpr size_t kNSCCRShift = 1; |
| |
| constexpr size_t kRPNDLCIShift = 2; |
| constexpr uint8_t kRPNDataBitsMask = 0b11; |
| constexpr size_t kRPNStopBitsShift = 2; |
| constexpr uint8_t kRPNStopBitsMask = 1; |
| constexpr size_t kRPNParityShift = 3; |
| constexpr uint8_t kRPNParityMask = 1; |
| constexpr size_t kRPNParityTypeShift = 4; |
| constexpr uint8_t kRPNParityTypeMask = 0b11; |
| |
| constexpr size_t kRLSDLCIShift = 2; |
| constexpr size_t kRLSErrorOccurredShift = 0; |
| constexpr uint8_t kRLSErrorOccurredMask = 1 << kRLSErrorOccurredShift; |
| constexpr size_t kRLSErrorShift = 1; |
| constexpr uint8_t kRLSErrorMask = 0b111; |
| |
| constexpr uint8_t kPNDLCIMask = 0b00111111; |
| constexpr uint8_t kPNPriorityMask = 0b00111111; |
| constexpr uint8_t kPNInitialCreditsMask = 0b00000111; |
| |
| // Length = 0, EA bit = 1. |
| constexpr uint8_t kFlowcontrolOnLength = 0b00000001; |
| constexpr uint8_t kFlowcontrolOffLength = 0b00000001; |
| |
| // The number of octets which form the header when the length field fits in one |
| // octet. This is thus the header size for all mux commands with the possible |
| // exception of the Test command. |
| constexpr size_t kMinHeaderSize = 2; |
| |
| // For a given payload |length|, calculates the number of octets needed to |
| // encode |length|. |
| // Note that this is only needed by the Test multiplexer command. |
| size_t NumLengthOctetsNeeded(size_t length) { |
| size_t l = length; |
| size_t counter = 0; |
| while (l) { |
| l >>= 1; |
| ++counter; |
| } |
| // Counter now contains the number of least significant bits which are |
| // occupied by meaningful data. Subtracting 1, counter becomes the index of |
| // the most significant bit containing a 1. |
| |
| // If counter is 0, no bits in |length| are set. We still need to return a |
| // single octet indicating a value of 0, though. |
| size_t num_octets = 1; |
| if (counter != 0) |
| num_octets = (counter - 1) / 7 + 1; |
| |
| return num_octets; |
| } |
| |
| // Turns a size_t into a buffer of length field octets as described in GSM |
| // 5.4.6.1. In this case, we are talking about the length field within the |
| // multiplexer control commands. |
| // GSM allows the length field to be specified with a variable number |
| // of octets. |
| // |
| // TODO(gusss): I can't actually find any bounds within the GSM or RFCOMM specs |
| // on how long this length field is allowed to be. Most of the multiplexer |
| // commands which we support have fixed-sized payloads (no larger than 8 octets, |
| // most of the time), so this isn't a problem. However, the Test command takes a |
| // user-supplied pattern of octets. There is no restriction in the spec on how |
| // long this pattern can be. |
| common::DynamicByteBuffer CreateLengthFieldOctets(size_t length) { |
| common::DynamicByteBuffer octets(NumLengthOctetsNeeded(length)); |
| |
| for (size_t i = 0; i < octets.size(); i++) { |
| // There should still be meaningful data left in length. |
| ZX_DEBUG_ASSERT(length); |
| // We set the EA bit to 0. |
| octets[i] = ~kEAMask & (length << kLengthShift); |
| length >>= 7; |
| } |
| // If we calculated the number of octets correctly above, then there should be |
| // nothing remaining in length. |
| ZX_DEBUG_ASSERT(length == 0); |
| |
| // Set the EA bit of the last octet to 1, to indicate it's the last octet. |
| octets[octets.size() - 1] |= kEAMask; |
| |
| return octets; |
| } |
| |
| // Compares |length| with the possible lengths for the information field of a |
| // multiplexer command of type |type|. |
| bool CommandLengthValid(MuxCommandType type, size_t length) { |
| switch (type) { |
| case MuxCommandType::kDLCParameterNegotiation: |
| return length == kPNLength; |
| case MuxCommandType::kTestCommand: |
| // Any length is valid for a Test command. |
| return true; |
| case MuxCommandType::kFlowControlOnCommand: |
| return length == kFConLength; |
| case MuxCommandType::kFlowControlOffCommand: |
| return length == kFCoffLength; |
| case MuxCommandType::kModemStatusCommand: |
| return length == kMSCWithBreakLength || length == kMSCWithoutBreakLength; |
| case MuxCommandType::kNonSupportedCommandResponse: |
| return length == kNSCLength; |
| case MuxCommandType::kRemoteLineStatusCommand: |
| return length == kRLSLength; |
| case MuxCommandType::kRemotePortNegotiationCommand: |
| return length == kRPNShortLength || length == kRPNLongLength; |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| MuxCommand::MuxCommand(MuxCommandType command_type, |
| CommandResponse command_response) |
| : command_type_(command_type), command_response_(command_response){}; |
| |
| std::unique_ptr<MuxCommand> MuxCommand::Parse( |
| const common::ByteBuffer& buffer) { |
| ZX_DEBUG_ASSERT_MSG(buffer.size() >= kMinHeaderSize, |
| "buffer must contain at least a type and length octet"); |
| |
| CommandResponse command_response = (buffer[kTypeIndex] & kCRMask) |
| ? CommandResponse::kCommand |
| : CommandResponse::kResponse; |
| MuxCommandType type = (MuxCommandType)(buffer[kTypeIndex] & kTypeMask); |
| |
| // Read the (potentially numerous) length octets. |
| size_t length = 0, length_idx = kLengthIndex, num_length_octets = 0; |
| do { |
| // Shift right to shift out the EA bit, and then shift into place. |
| length |= (buffer[length_idx] >> kLengthShift) << (7 * num_length_octets); |
| ++num_length_octets; |
| } while ((buffer[length_idx++] & kEAMask) == 0); |
| // 7*num_length_octets is the number of bits encoded by length. |
| // Here, we're limiting the size of the length field. In theory, the spec |
| // allows the length field to be of any size, but here we're limiting it to |
| // fitting in size_t. |
| if (7 * num_length_octets > 8 * sizeof(size_t)) { |
| bt_log(WARN, "rfcomm", "encoded length is larger than allowed"); |
| return nullptr; |
| } |
| |
| // Check that the buffer is actually at least as big as the command which it |
| // contains. Command is 1 control octet, multiple length octets, and payload. |
| if (buffer.size() < 1 + num_length_octets + length) { |
| bt_log(WARN, "rfcomm", "buffer is shorter than the command it contains"); |
| return nullptr; |
| } |
| |
| if (!CommandLengthValid(MuxCommandType(type), length)) { |
| bt_log(ERROR, "rfcomm", |
| "unexpected length %zu for multiplexer command of type %u", length, |
| static_cast<unsigned>(type)); |
| return nullptr; |
| } |
| |
| switch (type) { |
| case MuxCommandType::kDLCParameterNegotiation: |
| return DLCParameterNegotiationCommand::Parse(command_response, buffer); |
| |
| case MuxCommandType::kTestCommand: |
| return TestCommand::Parse(command_response, length, buffer); |
| |
| case MuxCommandType::kFlowControlOnCommand: |
| return FlowControlOnCommand::Parse(command_response); |
| |
| case MuxCommandType::kFlowControlOffCommand: |
| return FlowControlOffCommand::Parse(command_response); |
| |
| case MuxCommandType::kModemStatusCommand: |
| return ModemStatusCommand::Parse(command_response, length, buffer); |
| |
| case MuxCommandType::kNonSupportedCommandResponse: |
| return NonSupportedCommandResponse::Parse(command_response, buffer); |
| |
| case MuxCommandType::kRemotePortNegotiationCommand: |
| return RemotePortNegotiationCommand::Parse(command_response, length, |
| buffer); |
| |
| case MuxCommandType::kRemoteLineStatusCommand: |
| return RemoteLineStatusCommand::Parse(command_response, buffer); |
| |
| default: |
| bt_log(WARN, "rfcomm", "unrecognized multiplexer command type: %u", |
| static_cast<unsigned>(type)); |
| } |
| |
| return std::unique_ptr<MuxCommand>(nullptr); |
| } |
| |
| TestCommand::TestCommand(CommandResponse command_response, |
| const common::ByteBuffer& test_pattern) |
| : MuxCommand(MuxCommandType::kTestCommand, command_response) { |
| test_pattern_ = common::DynamicByteBuffer(test_pattern.size()); |
| test_pattern.Copy(&test_pattern_, 0, test_pattern.size()); |
| } |
| |
| std::unique_ptr<TestCommand> TestCommand::Parse( |
| CommandResponse command_response, size_t length, |
| const common::ByteBuffer& buffer) { |
| return std::make_unique<TestCommand>(command_response, |
| buffer.view(2, length)); |
| } |
| |
| void TestCommand::Write(common::MutableBufferView buffer) const { |
| ZX_ASSERT(buffer.size() >= written_size()); |
| |
| size_t idx = 0; |
| |
| buffer[idx] = type_field_octet(); |
| ++idx; |
| |
| // Write the length field octet(s). If the length fits in one byte (accounting |
| // for the EA bit), we write it immediately. This should be the common case. |
| if (test_pattern_.size() <= kMaxSingleOctetLength) { |
| buffer[idx] = kEAMask | (test_pattern_.size() << kLengthShift); |
| ++idx; |
| } else { |
| auto length_field_octets = CreateLengthFieldOctets(test_pattern_.size()); |
| buffer.Write(length_field_octets, idx); |
| idx += length_field_octets.size(); |
| } |
| |
| buffer.Write(test_pattern_, idx); |
| } |
| |
| size_t TestCommand::written_size() const { |
| return 1 // Type |
| + NumLengthOctetsNeeded(test_pattern_.size()) // Length |
| + test_pattern_.size(); // Payload |
| } |
| |
| FlowControlOnCommand::FlowControlOnCommand(CommandResponse command_response) |
| : MuxCommand(MuxCommandType::kFlowControlOnCommand, command_response) {} |
| |
| std::unique_ptr<FlowControlOnCommand> FlowControlOnCommand::Parse( |
| CommandResponse command_response) { |
| return std::make_unique<FlowControlOnCommand>(command_response); |
| } |
| |
| void FlowControlOnCommand::Write(common::MutableBufferView buffer) const { |
| ZX_ASSERT(buffer.size() >= written_size()); |
| buffer[kTypeIndex] = type_field_octet(); |
| // Length = 0, EA bit = 1. |
| buffer[kLengthIndex] = kFlowcontrolOnLength; |
| } |
| |
| size_t FlowControlOnCommand::written_size() const { return 2ul + kFConLength; } |
| |
| std::unique_ptr<FlowControlOffCommand> FlowControlOffCommand::Parse( |
| CommandResponse command_response) { |
| return std::make_unique<FlowControlOffCommand>(command_response); |
| } |
| |
| void FlowControlOffCommand::Write(common::MutableBufferView buffer) const { |
| ZX_ASSERT(buffer.size() >= written_size()); |
| buffer[kTypeIndex] = type_field_octet(); |
| // Length = 0, EA bit = 1. |
| buffer[kLengthIndex] = kFlowcontrolOffLength; |
| } |
| |
| FlowControlOffCommand::FlowControlOffCommand(CommandResponse command_response) |
| : MuxCommand(MuxCommandType::kFlowControlOffCommand, command_response) {} |
| |
| size_t FlowControlOffCommand::written_size() const { return 2ul + kFConLength; } |
| |
| ModemStatusCommand::ModemStatusCommand(CommandResponse command_response, |
| DLCI dlci, |
| ModemStatusCommandSignals signals, |
| BreakValue break_value) |
| : MuxCommand(MuxCommandType::kModemStatusCommand, command_response), |
| dlci_(dlci), |
| signals_(signals), |
| break_value_(break_value){}; |
| |
| std::unique_ptr<ModemStatusCommand> ModemStatusCommand::Parse( |
| CommandResponse command_response, size_t length, |
| const common::ByteBuffer& buffer) { |
| DLCI dlci = buffer[2] >> kMSCDLCIShift; |
| ModemStatusCommandSignals signals; |
| BreakValue break_value = kDefaultInvalidBreakValue; |
| |
| // The first bit of |buffer[4]| encodes whether the octet encodes a break |
| // signal. If it does not, then we let break_value default to an invalid |
| // value. |
| if (length == kMSCWithBreakLength && buffer[4] & kMSCBreakSignalMask) { |
| break_value = buffer[4] >> kMSCBreakValueShift; |
| } |
| |
| // clang-format off |
| signals.flow_control = buffer[3] & kMSCFlowControlMask; |
| signals.ready_to_communicate = buffer[3] & kMSCReadyToCommunicateMask; |
| signals.ready_to_receive = buffer[3] & kMSCReadyToReceiveMask; |
| signals.incoming_call = buffer[3] & kMSCIncomingCallMask; |
| signals.data_valid = buffer[3] & kMSCDataValidMask; |
| // clang-format on |
| |
| return std::make_unique<ModemStatusCommand>(command_response, dlci, signals, |
| break_value); |
| } |
| |
| void ModemStatusCommand::Write(common::MutableBufferView buffer) const { |
| ZX_ASSERT(buffer.size() >= written_size()); |
| buffer[kTypeIndex] = type_field_octet(); |
| // EA bit = 1. |
| buffer[kLengthIndex] = |
| ((has_break_signal() ? kMSCWithBreakLength : kMSCWithoutBreakLength) |
| << kLengthShift) | |
| kEAMask; |
| // EA bit = 1, bit 2 = 1. |
| buffer[2] = kEAMask | (1 << 1) | (dlci_ << kMSCDLCIShift); |
| // clang-format off |
| buffer[3] = !has_break_signal() |
| | signals_.flow_control << kMSCFlowControlShift |
| | signals_.ready_to_communicate << kMSCReadyToCommunicateShift |
| | signals_.ready_to_receive << kMSCReadyToReceiveShift |
| | signals_.incoming_call << kMSCIncomingCallShift |
| | signals_.data_valid << kMSCDataValidShift; |
| if (has_break_signal()) { |
| buffer[4] = kEAMask |
| | has_break_signal() << kMSCBreakSignalShift |
| | break_value_ << kMSCBreakValueShift; |
| // clang-format on |
| } |
| } |
| |
| size_t ModemStatusCommand::written_size() const { |
| return 2ul + |
| (has_break_signal() ? kMSCWithBreakLength : kMSCWithoutBreakLength); |
| } |
| |
| RemotePortNegotiationCommand::RemotePortNegotiationCommand( |
| CommandResponse command_response, DLCI dlci) |
| : MuxCommand(MuxCommandType::kRemotePortNegotiationCommand, |
| command_response), |
| short_RPN_command_(true), |
| dlci_(dlci), |
| params_(kDefaultRemotePortNegotiationParams), |
| mask_(kDefaultRemotePortNegotiationMaskBitfield) {} |
| |
| RemotePortNegotiationCommand::RemotePortNegotiationCommand( |
| CommandResponse command_response, DLCI dlci, |
| RemotePortNegotiationParams params, RemotePortNegotiationMaskBitfield mask) |
| : MuxCommand(MuxCommandType::kRemotePortNegotiationCommand, |
| command_response), |
| short_RPN_command_(false), |
| dlci_(dlci), |
| params_(params), |
| mask_(mask) {} |
| |
| std::unique_ptr<RemotePortNegotiationCommand> |
| RemotePortNegotiationCommand::Parse(CommandResponse command_response, |
| size_t length, |
| const common::ByteBuffer& buffer) { |
| DLCI dlci = buffer[2] >> kRPNDLCIShift; |
| |
| if (length == kRPNShortLength) { |
| return std::make_unique<RemotePortNegotiationCommand>(command_response, |
| dlci); |
| } |
| |
| RemotePortNegotiationParams params; |
| RemotePortNegotiationMaskBitfield mask; |
| |
| // TODO(gusss): again, this kind of casting is probably not a good idea. |
| params.baud = static_cast<Baud>(buffer[3]); |
| params.data_bits = static_cast<DataBits>(buffer[4] & kRPNDataBitsMask); |
| params.stop_bits = |
| static_cast<StopBits>(buffer[4] >> kRPNStopBitsShift & kRPNStopBitsMask); |
| params.parity = buffer[4] & kRPNParityMask << kRPNParityShift; |
| params.parity_type = static_cast<ParityType>( |
| buffer[4] >> kRPNParityTypeShift & kRPNParityTypeMask); |
| params.flow_control = buffer[5]; |
| params.xon_character = buffer[6]; |
| params.xoff_character = buffer[7]; |
| mask = buffer[8] << 8 | buffer[9]; |
| |
| return std::make_unique<RemotePortNegotiationCommand>( |
| command_response, dlci, std::move(params), std::move(mask)); |
| } |
| |
| void RemotePortNegotiationCommand::Write( |
| common::MutableBufferView buffer) const { |
| ZX_ASSERT(buffer.size() >= written_size()); |
| buffer[kTypeIndex] = type_field_octet(); |
| // EA bit = 1. |
| buffer[kLengthIndex] = |
| ((short_RPN_command_ ? kRPNShortLength : kRPNLongLength) |
| << kLengthShift) | |
| kEAMask; |
| // EA bit = 1, bit 2 = 1. |
| buffer[2] = kEAMask | (1 << 1) | (dlci_ << kRPNDLCIShift); |
| |
| if (short_RPN_command_) |
| return; |
| |
| // See GSM table 11. |
| buffer[3] = static_cast<uint8_t>(params_.baud); |
| buffer[4] = static_cast<uint8_t>(params_.data_bits); |
| buffer[4] |= (static_cast<bool>(params_.stop_bits) << kRPNStopBitsShift); |
| buffer[4] |= (params_.parity << kRPNParityShift); |
| buffer[4] |= |
| (static_cast<uint8_t>(params_.parity_type) << kRPNParityTypeShift); |
| buffer[5] = params_.flow_control; |
| buffer[6] = params_.xon_character; |
| buffer[7] = params_.xoff_character; |
| buffer[8] = static_cast<uint8_t>(mask_ >> 8); |
| buffer[9] = static_cast<uint8_t>(mask_); |
| } |
| |
| size_t RemotePortNegotiationCommand::written_size() const { |
| return 2ul + (short_RPN_command_ ? kRPNShortLength : kRPNLongLength); |
| } |
| |
| RemoteLineStatusCommand::RemoteLineStatusCommand( |
| CommandResponse command_response, DLCI dlci, bool error_occurred, |
| LineError error) |
| : MuxCommand(MuxCommandType::kRemoteLineStatusCommand, command_response), |
| dlci_(dlci), |
| error_occurred_(error_occurred), |
| error_(error) {} |
| |
| std::unique_ptr<RemoteLineStatusCommand> RemoteLineStatusCommand::Parse( |
| CommandResponse command_response, const common::ByteBuffer& buffer) { |
| DLCI dlci = buffer[2] >> kRLSDLCIShift; |
| bool error_occurred = buffer[3] & kRLSErrorOccurredMask; |
| // TODO(gusss) |
| LineError error = |
| static_cast<LineError>(buffer[3] >> kRLSErrorShift & kRLSErrorMask); |
| |
| return std::make_unique<RemoteLineStatusCommand>(command_response, dlci, |
| error_occurred, error); |
| } |
| |
| void RemoteLineStatusCommand::Write(common::MutableBufferView buffer) const { |
| ZX_ASSERT(buffer.size() >= written_size()); |
| buffer[kTypeIndex] = type_field_octet(); |
| // EA bit = 1. |
| buffer[kLengthIndex] = (kRLSLength << kLengthShift) | kEAMask; |
| // EA bit = 1, bit 2 = 1. |
| buffer[2] = kEAMask | (1 << 1) | (dlci_ << kRLSDLCIShift); |
| buffer[3] = |
| error_occurred_ | (static_cast<uint8_t>(error_) << kRLSErrorShift); |
| } |
| |
| size_t RemoteLineStatusCommand::written_size() const { |
| return 2ul + kRLSLength; |
| } |
| |
| NonSupportedCommandResponse::NonSupportedCommandResponse( |
| CommandResponse incoming_command_response, |
| uint8_t incoming_non_supported_command) |
| : MuxCommand(MuxCommandType::kNonSupportedCommandResponse, |
| CommandResponse::kResponse), |
| incoming_command_response_(incoming_command_response), |
| incoming_non_supported_command_(incoming_non_supported_command) {} |
| |
| std::unique_ptr<NonSupportedCommandResponse> NonSupportedCommandResponse::Parse( |
| CommandResponse command_response, const common::ByteBuffer& buffer) { |
| CommandResponse incoming_command_response = buffer[2] & kCRMask |
| ? CommandResponse::kCommand |
| : CommandResponse::kResponse; |
| uint8_t incoming_non_supported_command = |
| buffer[2] >> kNSCNotSupportedCommandShift; |
| |
| return std::make_unique<NonSupportedCommandResponse>( |
| incoming_command_response, incoming_non_supported_command); |
| } |
| |
| void NonSupportedCommandResponse::Write( |
| common::MutableBufferView buffer) const { |
| ZX_ASSERT(buffer.size() >= written_size()); |
| buffer[kTypeIndex] = type_field_octet(); |
| // EA bit = 1. |
| buffer[kLengthIndex] = (kNSCLength << kLengthShift) | kEAMask; |
| buffer[2] = kEAMask | |
| (incoming_command_response_ == CommandResponse::kCommand ? 1 : 0) |
| << kNSCCRShift | |
| incoming_non_supported_command_ << kNSCNotSupportedCommandShift; |
| } |
| |
| size_t NonSupportedCommandResponse::written_size() const { |
| return 2ul + kNSCLength; |
| } |
| |
| DLCParameterNegotiationCommand::DLCParameterNegotiationCommand( |
| CommandResponse command_response, ParameterNegotiationParams params) |
| : MuxCommand(MuxCommandType::kDLCParameterNegotiation, command_response), |
| params_(params) {} |
| |
| std::unique_ptr<DLCParameterNegotiationCommand> |
| DLCParameterNegotiationCommand::Parse(CommandResponse command_response, |
| const common::ByteBuffer& buffer) { |
| ParameterNegotiationParams params; |
| |
| params.dlci = buffer[2]; |
| params.credit_based_flow_handshake = static_cast<CreditBasedFlowHandshake>( |
| buffer[3] >> kPNCreditBasedFlowHandshakeShift); |
| params.priority = buffer[4]; |
| params.maximum_frame_size = buffer[6] | buffer[7] << 8; |
| params.initial_credits = buffer[9]; |
| |
| return std::make_unique<DLCParameterNegotiationCommand>(command_response, |
| params); |
| } |
| |
| void DLCParameterNegotiationCommand::Write( |
| common::MutableBufferView buffer) const { |
| ZX_ASSERT(buffer.size() >= written_size()); |
| buffer[kTypeIndex] = type_field_octet(); |
| // EA bit = 1. |
| buffer[kLengthIndex] = (kPNLength << kLengthShift) | kEAMask; |
| |
| buffer[2] = params_.dlci & kPNDLCIMask; |
| buffer[3] = static_cast<uint8_t>(params_.credit_based_flow_handshake) |
| << kPNCreditBasedFlowHandshakeShift; |
| buffer[4] = params_.priority & kPNPriorityMask; |
| buffer[5] = 0; |
| buffer[6] = static_cast<uint8_t>(params_.maximum_frame_size); |
| buffer[7] = static_cast<uint8_t>(params_.maximum_frame_size >> 8); |
| buffer[8] = 0; |
| buffer[9] = params_.initial_credits & kPNInitialCreditsMask; |
| } |
| |
| size_t DLCParameterNegotiationCommand::written_size() const { |
| return 2ul + kPNLength; |
| } |
| |
| } // namespace rfcomm |
| } // namespace btlib |