diff --git a/pw_bluetooth/BUILD.bazel b/pw_bluetooth/BUILD.bazel index 6a150b709..447739fb7 100644 --- a/pw_bluetooth/BUILD.bazel +++ b/pw_bluetooth/BUILD.bazel @@ -244,6 +244,19 @@ cc_library( deps = [":_emboss_l2cap_frames"], ) +emboss_cc_library( + name = "_emboss_rfcomm_frames", + srcs = ["public/pw_bluetooth/rfcomm_frames.emb"], + enable_enum_traits = False, + visibility = ["//visibility:private"], +) + +cc_library( + name = "emboss_rfcomm_frames", + includes = ["public"], + deps = [":_emboss_rfcomm_frames"], +) + cc_library( name = "emboss_hci", deps = [ @@ -266,6 +279,7 @@ pw_cc_test( ":emboss_hci", ":emboss_hci_test", ":emboss_l2cap_frames", + ":emboss_rfcomm_frames", "//third_party/fuchsia:stdcompat", ], ) diff --git a/pw_bluetooth/BUILD.gn b/pw_bluetooth/BUILD.gn index 4d4202f57..61a31b06e 100644 --- a/pw_bluetooth/BUILD.gn +++ b/pw_bluetooth/BUILD.gn @@ -167,6 +167,11 @@ if (dir_pw_third_party_emboss != "") { source = "public/pw_bluetooth/l2cap_frames.emb" } + emboss_cc_library("emboss_rfcomm_frames") { + public_configs = [ ":emboss_include_path" ] + source = "public/pw_bluetooth/rfcomm_frames.emb" + } + group("emboss_hci_group") { public_configs = [ ":emboss_include_path" ] public_deps = [ @@ -199,6 +204,8 @@ if (dir_pw_third_party_emboss != "") { } group("emboss_l2cap_frames") { } + group("emboss_rfcomm_frames") { + } } pw_test_group("tests") { @@ -254,6 +261,7 @@ pw_test("emboss_test") { ":emboss_hci_group", ":emboss_hci_test", ":emboss_l2cap_frames", + ":emboss_rfcomm_frames", "$dir_pw_third_party/fuchsia:stdcompat", ] } diff --git a/pw_bluetooth/CMakeLists.txt b/pw_bluetooth/CMakeLists.txt index b2393c5a0..8c6175095 100644 --- a/pw_bluetooth/CMakeLists.txt +++ b/pw_bluetooth/CMakeLists.txt @@ -157,6 +157,11 @@ emboss_cc_library(pw_bluetooth.emboss_l2cap_frames public/pw_bluetooth/l2cap_frames.emb ) +emboss_cc_library(pw_bluetooth.emboss_rfcomm_frames + SOURCES + public/pw_bluetooth/rfcomm_frames.emb +) + emboss_cc_library(pw_bluetooth.emboss_att SOURCES public/pw_bluetooth/att.emb @@ -197,6 +202,7 @@ pw_target_link_targets("pw_bluetooth._public_config" pw_bluetooth.emboss_hci_events pw_bluetooth.emboss_hci_h4 pw_bluetooth.emboss_l2cap_frames + pw_bluetooth.emboss_rfcomm_frames ) pw_add_test(pw_bluetooth.emboss_test @@ -210,6 +216,7 @@ pw_add_test(pw_bluetooth.emboss_test pw_bluetooth.emboss_hci_group pw_bluetooth.emboss_hci_test pw_bluetooth.emboss_l2cap_frames + pw_bluetooth.emboss_rfcomm_frames pw_third_party.fuchsia.stdcompat GROUPS modules diff --git a/pw_bluetooth/emboss_test.cc b/pw_bluetooth/emboss_test.cc index 6c2c9160a..49d92425b 100644 --- a/pw_bluetooth/emboss_test.cc +++ b/pw_bluetooth/emboss_test.cc @@ -29,6 +29,7 @@ #include "pw_bluetooth/hci_test.emb.h" #include "pw_bluetooth/hci_android.emb.h" // IWYU pragma: keep #include "pw_bluetooth/l2cap_frames.emb.h" // IWYU pragma: keep +#include "pw_bluetooth/rfcomm_frames.emb.h" // IWYU pragma: keep // clang-format on namespace pw::bluetooth { diff --git a/pw_bluetooth/public/pw_bluetooth/rfcomm_frames.emb b/pw_bluetooth/public/pw_bluetooth/rfcomm_frames.emb new file mode 100644 index 000000000..b48c08422 --- /dev/null +++ b/pw_bluetooth/public/pw_bluetooth/rfcomm_frames.emb @@ -0,0 +1,103 @@ +# Copyright 2024 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. + +# This file contains Emboss definitions for the logical link control and +# adaptation protocol (l2cap) frames found in the Bluetooth core specification. +# The Emboss compiler is used to generate a C++ header from this file. + +[$default byte_order: "LittleEndian"] +[(cpp) namespace: "pw::bluetooth::emboss"] +# ========================= RFCOMM Frame Format ================================= +# RFCOMM Spec v1.2 +# Also see: ETSI TS 07.10 + +enum RfcommFrameType: + -- ETSI TS 07.10 section 5.2.1.3 + [maximum_bits: 8] + SET_ASYNC_BALANCED_MODE = 0x2f + UNNUMBERED_ACK = 0x63 + DISCONNECT_MODE = 0x0f + DISCONNECT = 0xc3 + UNNUMBERED_INFORMATION_WITH_HEADER_CHECK = 0xef + UNNUMBERED_INFORMATION_WITH_HEADER_CHECK_AND_POLL_FINAL = 0xff + -- RFCOMM extension. With Poll/Final bit set in UIH control byte, a credits + -- field is the first byte of the payload. + +enum RfcommFixedChannel: + [maximum_bits: 8] + CONTROL = 0 + +enum RfcommCommandResponse: + -- Commands from the initiator and responses from the responder + -- have C/R = 0. Commands from the responder and responses from + -- the initiator have C/R = 1. + [maximum_bits: 1] + COMMAND = 0 + RESPONSE = 1 + +enum RfcommDirection: + [maximum_bits: 1] + RESPONDER = 0 + INITIATOR = 1 + +enum RfcommLengthExtended: + [maximum_bits: 1] + NORMAL = 1 + EXTENDED = 0 + +struct RfcommFrame: + -- RFCOMM ETSI TS 07.10 frame + [requires: extended_address == true] + 0 [+1] bits: + 0 [+1] Flag extended_address + 1 [+1] RfcommCommandResponse command_response + 2 [+1] RfcommDirection direction + 3 [+5] UInt channel + -- ETSI TS 07.10 section 5.2.1.2 + + 1 [+1] RfcommFrameType control + -- ETSI TS 07.10 section 5.2.1.3 + + 2 [+1] bits: + 0 [+1] RfcommLengthExtended length_extended_flag + 1 [+7] UInt length + -- ETSI TS 07.10 section 5.2.1.4 + + if length_extended_flag == RfcommLengthExtended.EXTENDED: + 3 [+1] UInt length_extended + -- ETSI TS 07.10 section 5.2.1.4 + + let information_length = $present(length_extended) ? length * 256 + length_extended : length + -- Length of the '5.2.1.4 Information Field'. May include the RFCOMM credits + -- field which appears at start. + + let credits_offset = $present(length_extended) ? 4 : 3 + if control == RfcommFrameType.UNNUMBERED_INFORMATION_WITH_HEADER_CHECK_AND_POLL_FINAL && channel != 0: + credits_offset [+1] UInt credits + -- Credits field can appears as first byte of information when Poll/Final + -- bit is set on UIH frames and channel is not control (0). + + let payload_length = $present(credits) ? information_length - 1 : information_length + let payload_offset = $present(credits) ? credits_offset + 1 : credits_offset + payload_offset [+payload_length] UInt:8[payload_length] payload + -- Payload is the RFCOMM payload contained in the 'Information Field' which + -- may start with credits. + + let fcs_offset = payload_offset + payload_length + fcs_offset [+1] UInt fcs + -- Frame checksum. See: GSM 07.10 TS 101 369 V6.3.0 + -- SABM, DISC, UA, DM frame types: + -- FCS should be calculated over address, control and length fields. + -- UIH frame type: + -- FCS should be calculated over address and control fields.