// Copyright 2023 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. #include "pw_bluetooth_sapphire/internal/host/l2cap/low_energy_command_handler.h" #include #include #include "pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h" namespace bt::l2cap::internal { namespace { // Helpers to make converting values less verbose. // TODO(fxbug.dev/373673027) - Remove once we use Emboss. template T Serialize(T value) { return pw::bytes::ConvertOrderTo(cpp20::endian::little, value); } template T SerializeEnum(T value) { return static_cast(pw::bytes::ConvertOrderTo( cpp20::endian::little, static_cast>(value))); } template T Deserialize(T value) { return pw::bytes::ConvertOrderFrom(cpp20::endian::little, value); } template T DeserializeEnum(T value) { return static_cast(pw::bytes::ConvertOrderFrom( cpp20::endian::little, static_cast>(value))); } } // namespace bool LowEnergyCommandHandler::ConnectionParameterUpdateResponse::Decode( const ByteBuffer& payload_buf) { const uint16_t result = pw::bytes::ConvertOrderFrom( cpp20::endian::little, static_cast(payload_buf.ReadMember<&PayloadT::result>())); result_ = ConnectionParameterUpdateResult{result}; return true; } bool LowEnergyCommandHandler::LeCreditBasedConnectionResponse::Decode( const ByteBuffer& payload_buf) { const uint16_t destination_cid = pw::bytes::ConvertOrderFrom( cpp20::endian::little, static_cast(payload_buf.ReadMember<&PayloadT::dst_cid>())); destination_cid_ = ChannelId{destination_cid}; const uint16_t mtu = pw::bytes::ConvertOrderFrom( cpp20::endian::little, static_cast(payload_buf.ReadMember<&PayloadT::mtu>())); mtu_ = mtu; const uint16_t mps = pw::bytes::ConvertOrderFrom( cpp20::endian::little, static_cast(payload_buf.ReadMember<&PayloadT::mps>())); mps_ = mps; const uint16_t initial_credits = pw::bytes::ConvertOrderFrom( cpp20::endian::little, static_cast( payload_buf.ReadMember<&PayloadT::initial_credits>())); initial_credits_ = initial_credits; const uint16_t result = pw::bytes::ConvertOrderFrom( cpp20::endian::little, static_cast(payload_buf.ReadMember<&PayloadT::result>())); result_ = LECreditBasedConnectionResult{result}; return true; } LowEnergyCommandHandler::ConnectionParameterUpdateResponder:: ConnectionParameterUpdateResponder( SignalingChannel::Responder* sig_responder) : Responder(sig_responder) {} void LowEnergyCommandHandler::ConnectionParameterUpdateResponder::Send( ConnectionParameterUpdateResult result) { ConnectionParameterUpdateResponsePayload payload; payload.result = ConnectionParameterUpdateResult{pw::bytes::ConvertOrderTo( cpp20::endian::little, static_cast(result))}; sig_responder_->Send(BufferView(&payload, sizeof(payload))); } LowEnergyCommandHandler::LeCreditBasedConnectionResponder:: LeCreditBasedConnectionResponder(SignalingChannel::Responder* sig_responder) : Responder(sig_responder) {} void LowEnergyCommandHandler::LeCreditBasedConnectionResponder::Send( ChannelId destination_cid, uint16_t mtu, uint16_t mps, uint16_t initial_credits, LECreditBasedConnectionResult result) { LECreditBasedConnectionResponsePayload payload; payload.dst_cid = Serialize(destination_cid); payload.mtu = Serialize(mtu); payload.mps = Serialize(mps); payload.initial_credits = Serialize(initial_credits); payload.result = SerializeEnum(result); sig_responder_->Send(BufferView(&payload, sizeof(payload))); } LowEnergyCommandHandler::LowEnergyCommandHandler( SignalingChannelInterface* sig, fit::closure request_fail_callback) : CommandHandler(sig, std::move(request_fail_callback)) {} bool LowEnergyCommandHandler::SendLeCreditBasedConnectionRequest( uint16_t psm, uint16_t cid, uint16_t mtu, uint16_t mps, uint16_t credits, SendLeCreditBasedConnectionRequestCallback cb) { auto on_le_credit_based_connection_rsp = BuildResponseHandler(std::move(cb)); LECreditBasedConnectionRequestPayload payload; payload.le_psm = pw::bytes::ConvertOrderTo(cpp20::endian::little, psm); payload.src_cid = pw::bytes::ConvertOrderTo(cpp20::endian::little, cid); payload.mtu = pw::bytes::ConvertOrderTo(cpp20::endian::little, mtu); payload.mps = pw::bytes::ConvertOrderTo(cpp20::endian::little, mps); payload.initial_credits = pw::bytes::ConvertOrderTo(cpp20::endian::little, credits); return sig()->SendRequest(kLECreditBasedConnectionRequest, BufferView(&payload, sizeof(payload)), std::move(on_le_credit_based_connection_rsp)); } bool LowEnergyCommandHandler::SendConnectionParameterUpdateRequest( uint16_t interval_min, uint16_t interval_max, uint16_t peripheral_latency, uint16_t timeout_multiplier, ConnectionParameterUpdateResponseCallback cb) { auto on_param_update_rsp = BuildResponseHandler(std::move(cb)); ConnectionParameterUpdateRequestPayload payload; payload.interval_min = pw::bytes::ConvertOrderTo(cpp20::endian::little, interval_min); payload.interval_max = pw::bytes::ConvertOrderTo(cpp20::endian::little, interval_max); payload.peripheral_latency = pw::bytes::ConvertOrderTo(cpp20::endian::little, peripheral_latency); payload.timeout_multiplier = pw::bytes::ConvertOrderTo(cpp20::endian::little, timeout_multiplier); return sig()->SendRequest(kConnectionParameterUpdateRequest, BufferView(&payload, sizeof(payload)), std::move(on_param_update_rsp)); } void LowEnergyCommandHandler::ServeConnectionParameterUpdateRequest( ConnectionParameterUpdateRequestCallback callback) { auto on_param_update_req = [cb = std::move(callback)]( const ByteBuffer& request_payload, SignalingChannel::Responder* sig_responder) { if (request_payload.size() != sizeof(ConnectionParameterUpdateRequestPayload)) { bt_log(DEBUG, "l2cap-le", "cmd: rejecting malformed Connection Parameter Update Request, " "size %zu", request_payload.size()); sig_responder->RejectNotUnderstood(); return; } const auto& req = request_payload.To(); const uint16_t interval_min = pw::bytes::ConvertOrderFrom(cpp20::endian::little, req.interval_min); const uint16_t interval_max = pw::bytes::ConvertOrderFrom(cpp20::endian::little, req.interval_max); const uint16_t peripheral_latency = pw::bytes::ConvertOrderFrom( cpp20::endian::little, req.peripheral_latency); const uint16_t timeout_multiplier = pw::bytes::ConvertOrderFrom( cpp20::endian::little, req.timeout_multiplier); ConnectionParameterUpdateResponder responder(sig_responder); cb(interval_min, interval_max, peripheral_latency, timeout_multiplier, &responder); }; sig()->ServeRequest(kConnectionParameterUpdateRequest, std::move(on_param_update_req)); } void LowEnergyCommandHandler::ServeLeCreditBasedConnectionRequest( LeCreditBasedConnectionRequestCallback callback) { using Request = LECreditBasedConnectionRequestPayload; auto on_le_credit_based_connection_request = [cb = std::move(callback)](const ByteBuffer& request_payload, SignalingChannel::Responder* sig_responder) { if (request_payload.size() != sizeof(Request)) { bt_log(DEBUG, "l2cap-le", "cmd: rejecting malformed LE Credit-based Connection Request, " "size %zu", request_payload.size()); sig_responder->RejectNotUnderstood(); return; } LeCreditBasedConnectionResponder responder(sig_responder); cb(Deserialize(request_payload.ReadMember<&Request::le_psm>()), Deserialize(request_payload.ReadMember<&Request::src_cid>()), Deserialize(request_payload.ReadMember<&Request::mtu>()), Deserialize(request_payload.ReadMember<&Request::mps>()), Deserialize(request_payload.ReadMember<&Request::initial_credits>()), &responder); }; sig()->ServeRequest(kLECreditBasedConnectionRequest, std::move(on_le_credit_based_connection_request)); } } // namespace bt::l2cap::internal