pw_bluetooth: Migrate Emboss files from Fuchsia

Migrate Fuchsia Bluetooth Emboss files to pw_bluetooth.

Update GN emboss_cc_library template to support imports.

Add documentation above emboss_cc_library.

Bug: b/265052417
Test: I compiled Fuchsia with this CL and fxr/793294.

Change-Id: Iebd6412445b22bbce7985da707015c98272251b9
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/126942
Commit-Queue: Ben Lawson <benlawson@google.com>
Reviewed-by: Ali Saeed <saeedali@google.com>
diff --git a/pw_bluetooth/BUILD.gn b/pw_bluetooth/BUILD.gn
index 274c865..f9b9fd1 100644
--- a/pw_bluetooth/BUILD.gn
+++ b/pw_bluetooth/BUILD.gn
@@ -14,6 +14,7 @@
 
 import("//build_overrides/pigweed.gni")
 
+import("$dir_pigweed/third_party/emboss/build_defs.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_chrono/backend.gni")
 import("$dir_pw_docgen/docs.gni")
@@ -67,6 +68,25 @@
   ]
 }
 
+if (dir_pw_third_party_emboss != "") {
+  config("emboss_include_path") {
+    include_dirs = [ "${target_gen_dir}/public" ]
+    visibility = [ ":*" ]
+  }
+
+  emboss_cc_library("emboss_hci") {
+    public_configs = [ ":emboss_include_path" ]
+    source = "public/pw_bluetooth/hci.emb"
+  }
+
+  emboss_cc_library("emboss_vendor") {
+    public_configs = [ ":emboss_include_path" ]
+    source = "public/pw_bluetooth/vendor.emb"
+    imports = [ "public/pw_bluetooth/hci.emb" ]
+    deps = [ ":emboss_hci" ]
+  }
+}
+
 pw_test_group("tests") {
   enable_if = pw_chrono_SYSTEM_CLOCK_BACKEND != ""
   tests = [
diff --git a/pw_bluetooth/public/pw_bluetooth/hci.emb b/pw_bluetooth/public/pw_bluetooth/hci.emb
new file mode 100644
index 0000000..6e2c7a1
--- /dev/null
+++ b/pw_bluetooth/public/pw_bluetooth/hci.emb
@@ -0,0 +1,957 @@
+# 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.
+
+# This file contains Emboss definitions for Host Controller Interface packets
+# and types 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"]
+# =========================== Constants =================================
+
+
+enum CodingFormat:
+  -- Coding formats from assigned numbers.
+  -- (https://www.bluetooth.com/specifications/assigned-numbers/host-controller-interface)
+  [maximum_bits: 8]
+  U_LAW           = 0x00
+  A_LAW           = 0x01
+  CVSD            = 0x02
+  TRANSPARENT     = 0x03
+  LINEAR_PCM      = 0x04
+  MSBC            = 0x05
+  LC3             = 0x06
+  G729A           = 0x07
+  VENDOR_SPECIFIC = 0xFF
+
+
+enum GenericEnableParam:
+  -- Binary values that can be generically passed to HCI commands that expect a
+  -- 1-octet boolean "enable"/"disable" parameter.
+  [maximum_bits: 8]
+  DISABLE = 0x00
+  ENABLE  = 0x01
+
+
+enum InquiryAccessCode:
+  -- General- and Device-specific Inquiry Access Codes (DIACs) for use in Inquiry
+  -- command LAP fields.
+  -- (https://www.bluetooth.com/specifications/assigned-numbers/baseband)
+  [maximum_bits: 24]
+  GIAC = 0x9E8B33
+    -- General Inquiry Access Code
+
+  LIAC = 0x9E8B00
+    -- Limited Dedicated Inquiry Access Code
+
+
+enum PcmDataFormat:
+  -- PCM data formats from assigned numbers.
+  -- (https://www.bluetooth.com/specifications/assigned-numbers/host-controller-interface)
+  [maximum_bits: 8]
+  NOT_APPLICABLE  = 0x00
+  ONES_COMPLEMENT = 0x01
+  TWOS_COMPLEMENT = 0x02
+  SIGN_MAGNITUDE  = 0x03
+  UNSIGNED        = 0x04
+
+
+enum ScoDataPath:
+  [maximum_bits: 8]
+  HCI             = 0x00
+  AUDIO_TEST_MODE = 0xFF
+    -- 0x01 - 0xFE specify the logical channel number (vendor specific)
+
+
+enum ConnectionRole:
+  [maximum_bits: 8]
+  CENTRAL    = 0x00
+  PERIPHERAL = 0x01
+
+
+enum PageTimeout:
+  [maximum_bits: 16]
+  MIN     = 0x0001
+  MAX     = 0xFFFF
+  DEFAULT = 0x2000
+
+
+enum ScanInterval:
+  -- The minimum and maximum range values for Page and Inquiry Scan Interval (in time slices)
+  -- Page Scan Interval: (see Core Spec v5.0, Vol 2, Part E, Section 7.3.19)
+  -- Inquiry Scan Interval: (see Core Spec v5.0, Vol 2, Part E, Section 7.3.21)
+  [maximum_bits: 16]
+  MIN = 0x0012
+  MAX = 0x1000
+
+
+enum ScanWindow:
+  -- The minimum and maximum range valeus for Page and Inquiry Scan Window (in time slices)
+  -- Page Scan Window: (see Core Spec v5.0, Vol 2, Part E, Section 7.3.19)
+  -- Inquiry Scan Window: (see Core Spec v5.0, Vol 2, Part E, Section 7.3.21)
+  [maximum_bits: 16]
+  MIN = 0x0011
+  MAX = 0x1000
+
+
+enum StatusCode:
+  -- HCI Error Codes. Refer to Core Spec v5.0, Vol 2, Part D for definitions and
+  -- descriptions. All enum values are in increasing numerical order, however the
+  -- values are listed below for clarity.
+  [maximum_bits: 8]
+  SUCCESS                                           = 0x00
+  UNKNOWN_COMMAND                                   = 0x01
+  UNKNOWN_CONNECTION_ID                             = 0x02
+  HARDWARE_FAILURE                                  = 0x03
+  PAGE_TIMEOUT                                      = 0x04
+  AUTHENTICATION_FAILURE                            = 0x05
+  PIN_OR_KEY_MISSING                                = 0x06
+  MEMORY_CAPACITY_EXCEEDED                          = 0x07
+  CONNECTION_TIMEOUT                                = 0x08
+  CONNECTION_LIMIT_EXCEEDED                         = 0x09
+  SYNCHRONOUS_CONNECTION_LIMIT_EXCEEDED             = 0x0A
+  CONNECTION_ALREADY_EXISTS                         = 0x0B
+  COMMAND_DISALLOWED                                = 0x0C
+  CONNECTION_REJECTED_LIMITED_RESOURCES             = 0x0D
+  CONNECTION_REJECTED_SECURITY                      = 0x0E
+  CONNECTION_REJECTED_BAD_BD_ADDR                   = 0x0F
+  CONNECTION_ACCEPT_TIMEOUT_EXCEEDED                = 0x10
+  UNSUPPORTED_FEATURE_OR_PARAMETER                  = 0x11
+  INVALID_HCI_COMMAND_PARAMETERS                    = 0x12
+  REMOTE_USER_TERMINATED_CONNECTION                 = 0x13
+  REMOTE_DEVICE_TERMINATED_CONNECTION_LOW_RESOURCES = 0x14
+  REMOTE_DEVICE_TERMINATED_CONNECTION_POWER_OFF     = 0x15
+  CONNECTION_TERMINATED_BY_LOCAL_HOST               = 0x16
+  REPEATED_ATTEMPTS                                 = 0x17
+  PAIRING_NOT_ALLOWED                               = 0x18
+  UNKNOWN_LMP_PDU                                   = 0x19
+  UNSUPPORTED_REMOTE_FEATURE                        = 0x1A
+  SCO_OFFSET_REJECTED                               = 0x1B
+  SCO_INTERVAL_REJECTED                             = 0x1C
+  SCO_AIRMODE_REJECTED                              = 0x1D
+  INVALID_LMP_OR_LL_PARAMETERS                      = 0x1E
+  UNSPECIFIED_ERROR                                 = 0x1F
+  UNSUPPORTED_LMP_OR_LL_PARAMETER_VALUE             = 0x20
+  ROLE_CHANGE_NOT_ALLOWED                           = 0x21
+  LMP_OR_LL_RESPONSE_TIMEOUT                        = 0x22
+  LMP_ERROR_TRANSACTION_COLLISION                   = 0x23
+  LMP_PDU_NOT_ALLOWED                               = 0x24
+  ENCRYPTION_MODE_NOT_ACCEPTABLE                    = 0x25
+  LINK_KEY_CANNOT_BE_CHANGED                        = 0x26
+  REQUESTED_QOS_NOT_SUPPORTED                       = 0x27
+  INSTANT_PASSED                                    = 0x28
+  PAIRING_WITH_UNIT_KEY_NOT_SUPPORTED               = 0x29
+  DIFFERENT_TRANSACTION_COLLISION                   = 0x2A
+  RESERVED_0                                        = 0x2B
+  QOS_UNACCEPTABLE_PARAMETER                        = 0x2C
+  QOS_REJECTED                                      = 0x2D
+  CHANNEL_CLASSIFICATION_NOT_SUPPORTED              = 0x2E
+  INSUFFICIENT_SECURITY                             = 0x2F
+  PARAMETER_OUT_OF_MANDATORY_RANGE                  = 0x30
+  RESERVED_1                                        = 0x31
+  ROLE_SWITCH_PENDING                               = 0x32
+  RESERVED_2                                        = 0x33
+  RESERVED_SLOT_VIOLATION                           = 0x34
+  ROLE_SWITCH_FAILED                                = 0x35
+  EXTENDED_INQUIRY_RESPONSE_TOO_LARGE               = 0x36
+  SECURE_SIMPLE_PAIRING_NOT_SUPPORTED_BY_HOST       = 0x37
+  HOST_BUSY_PAIRING                                 = 0x38
+  CONNECTION_REJECTED_NO_SUITABLE_CHANNEL_FOUND     = 0x39
+  CONTROLLER_BUSY                                   = 0x3A
+  UNACCEPTABLE_CONNECTION_PARAMETERS                = 0x3B
+  DIRECTED_ADVERTISING_TIMEOUT                      = 0x3C
+  CONNECTION_TERMINATED_MIC_FAILURE                 = 0x3D
+  CONNECTION_FAILED_TO_BE_ESTABLISHED               = 0x3E
+  MAC_CONNECTION_FAILED                             = 0x3F
+  COARSE_CLOCK_ADJUSTMENT_REJECTED                  = 0x40
+  # 5.0
+  TYPE_0_SUBMAP_NOT_DEFINED                         = 0x41
+  UNKNOWN_ADVERTISING_IDENTIFIER                    = 0x42
+  LIMIT_REACHED                                     = 0x43
+  OPERATION_CANCELLED_BY_HOST                       = 0x44
+
+
+bits ScoPacketType:
+  -- Bitmask of SCO packet types.
+  # SCO packet types
+  0     [+1]  Flag  hv1
+  $next [+1]  Flag  hv2
+  $next [+1]  Flag  hv3
+  # eSCO packet types
+  $next [+1]  Flag  ev3
+  $next [+1]  Flag  ev4
+  $next [+1]  Flag  ev5
+  $next [+1]  Flag  not_2_ev3
+  $next [+1]  Flag  not_3_ev3
+  $next [+1]  Flag  not_2_ev5
+  $next [+1]  Flag  not_3_ev5
+  $next [+6]  UInt  padding
+
+
+bits PacketType:
+  -- Bitmask values for supported Packet Types
+  -- Used for HCI_Create_Connection and HCI_Change_Connection_Packet_Type
+  -- All other bits reserved for future use.
+  1  [+1]  Flag  disable_2_dh1
+  2  [+1]  Flag  disable_3_dh1
+  3  [+1]  Flag  enable_dm1     # Note: always on in >= v1.2
+  4  [+1]  Flag  enable_dh1
+  8  [+1]  Flag  disable_2_dh3
+  9  [+1]  Flag  disable_3_dh3
+  10 [+1]  Flag  enable_dm3
+  11 [+1]  Flag  enable_dh3
+  12 [+1]  Flag  disable_2_dh5
+  13 [+1]  Flag  disable_3_dh5
+  14 [+1]  Flag  enable_dm5
+  15 [+1]  Flag  enable_dh5
+
+
+enum PageScanRepetitionMode:
+  -- The page scan repetition mode, representing a maximum time between Page Scans.
+  -- (See Core Spec v5.0, Volume 2, Part B, Section 8.3.1)
+  [maximum_bits: 8]
+  R0_ = 0x00  # Continuous Scan
+  R1_ = 0x01  # <= 1.28s
+  R2_ = 0x02  # <= 2.56s
+
+
+bits ClockOffset:
+  -- Clock Offset. The lower 15 bits are set to the clock offset as retrieved
+  -- by an Inquiry. The highest bit is set to 1 if the rest of this parameter
+  -- is valid.
+  15 [+1]     Flag  valid
+  if valid:
+    0  [+15]  UInt  clock_offset
+
+
+struct BdAddr:
+  -- Bluetooth Device Address
+  0 [+6]  UInt  bd_addr
+
+
+enum IoCapability:
+  -- All other values reserved for future use.
+  [maximum_bits: 8]
+  DISPLAY_ONLY       = 0x00
+  DISPLAY_YES_NO     = 0x01
+  KEYBOARD_ONLY      = 0x02
+  NO_INPUT_NO_OUTPUT = 0x03
+
+
+enum OobDataPresent:
+  -- Whether there is out-of-band data present, and what type.
+  -- All other values reserved for future use.
+  [maximum_bits: 8]
+  NOT_PRESENT   = 0x00
+  P192_         = 0x01
+  P256_         = 0x02
+  P192_AND_P256 = 0x03
+
+# inclusive-language: disable
+enum AuthenticationRequirements:
+  -- All options without MITM do not require MITM protection, and a numeric
+  -- comparison with automatic accept is allowed.
+  -- All options with MITM do require MITM protection, and IO capabilities should
+  -- be used to determine the authentication procedure.
+  [maximum_bits: 8]
+  NO_BONDING             = 0x00
+  MITM_NO_BONDING        = 0x01
+  DEDICATED_BONDING      = 0x02
+  MITM_DEDICATED_BONDING = 0x03
+  GENERAL_BONDING        = 0x04
+  MITM_GENERAL_BONDING   = 0x05
+# inclusive-language: enable
+
+
+bits ScanEnableBits:
+  -- Bitmask Values for the Scan_Enable parameter in a
+  -- HCI_(Read,Write)_Scan_Enable command.
+  0     [+1]  Flag  inquiry
+    -- Inquiry scan enabled
+
+  $next [+1]  Flag  page
+    -- Page scan enabled
+
+  $next [+6]  UInt  padding
+
+
+enum InquiryScanType:
+  [maximum_bits: 8]
+  STANDARD   = 0x00
+    -- Standard scan (Default) (Mandatory)
+
+  INTERLACED = 0x01
+
+
+struct LocalName:
+  0 [+248]  UInt:8[248]  local_name
+
+
+struct ExtendedInquiryResponse:
+  0 [+240]  UInt:8[240]  extended_inquiry_response
+
+
+enum LEExtendedDuplicateFilteringOption:
+  -- Possible values that can be used for the |filter_duplicates| parameter in a
+  -- HCI_LE_Set_Extended_Scan_Enable command.
+  [maximum_bits: 8]
+  DISABLED                           = 0x00
+  ENABLED                            = 0x01
+  ENABLED_RESET_FOR_EACH_SCAN_PERIOD = 0x02
+    -- Duplicate advertisements in a single scan period should not be sent to the
+    -- Host in advertising report events; this setting shall only be used if the
+    -- Period parameter is non-zero.
+
+# ========================= HCI packet headers ==========================
+
+
+bits OpCodeBits:
+  # Emboss currently lacks support for default field values and cross-type integral equality.
+  # (https://github.com/google/emboss/issues/21)
+  # (https://github.com/google/emboss/issues/23)
+  # Upon the addition of these features, we will transition OpCodeBits to be a parameterized
+  # field which defaults for each HCI packet type to its corresponding OpCode.
+  0     [+10]  UInt  ocf
+  $next [+6]   UInt  ogf
+
+
+struct CommandHeader:
+  -- HCI Command packet header.
+  0     [+2]  OpCodeBits  opcode
+  $next [+1]  UInt        parameter_total_size
+
+
+struct EventHeader:
+  -- HCI Event packet header.
+  0     [+1]  UInt  event_code
+  $next [+1]  UInt  parameter_total_size
+
+# ========================= HCI Command packets =========================
+# Core Spec v5.3 Vol 4, Part E, Section 7
+
+
+struct InquiryCommand:
+  -- Inquiry Command (v1.1) (BR/EDR)
+  --
+  -- Note: NO Command Complete; Sends Inquiry Complete at the end of the
+  -- inquiry to indicate it's completion. No Inquiry Complete event is sent if
+  -- Inquiry is cancelled.
+
+  let hdr_size = CommandHeader.$size_in_bytes
+
+  0     [+hdr_size]  CommandHeader      header
+
+  $next [+3]         InquiryAccessCode  lap
+    -- LAP (Lower Address Part)
+    -- In the range 0x9E8B00 - 0x9E8B3F, defined by the Bluetooth SIG in
+    -- Baseband Assigned Numbers.
+
+  $next [+1]         UInt               inquiry_length
+    -- Time before the inquiry is halted. Defined in 1.28s units.
+    -- Range: 0x01 to kInquiryLengthMax in hci_constants.h
+
+  $next [+1]         UInt               num_responses
+    -- Maximum number of responses before inquiry is halted.
+    -- Set to 0x00 for unlimited.
+
+
+struct InquiryCancelCommand:
+  -- Inquiry Cancel Command (v1.1) (BR/EDR)
+  -- No command parameters
+  let hdr_size = CommandHeader.$size_in_bytes
+  0 [+hdr_size]  CommandHeader  header
+
+
+struct CreateConnectionCommand:
+  -- Create Connection (v1.1) (BR/EDR)
+  --
+  -- NOTE on ReturnParams: No Command Complete event will be sent by the
+  -- Controller to indicate that this command has been completed. Instead, the
+  -- Connection Complete event will indicate that this command has been
+  -- completed.
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]               CommandHeader           header
+  $next [+BdAddr.$size_in_bytes]  BdAddr                  bd_addr
+    -- BD_ADDR of the device to be connected
+
+  $next [+2]                      PacketType              packet_type
+    -- Mask of allowable packet types.
+
+  $next [+1]                      PageScanRepetitionMode  page_scan_repetition_mode
+    -- The Page Scan Repetition Mode of the remote device as retrieved by Inquiry.
+
+  $next [+1]                      UInt                    reserved
+    [requires: this == 0]
+
+  $next [+2]                      ClockOffset             clock_offset
+    -- Clock Offset. The lower 15 bits are set to the clock offset as retrieved
+    -- by an Inquiry. The highest bit is set to 1 if the rest of this parameter
+    -- is valid.
+
+  $next [+1]                      GenericEnableParam      allow_role_switch
+    -- Allow Role Switch.
+    -- Allowed values:
+    --  0x00 - No role switch allowed, this device will be the central
+    --  0x01 - Role switch allowed, this device may become peripheral during
+    --  connection setup
+
+
+struct DisconnectCommand:
+  -- Disconnect Command (v1.1) (BR/EDR & LE)
+  --
+  -- NOTE on ReturnParams: No Command Complete event will be sent by the
+  -- Controller to indicate that this command has been completed. Instead, the
+  -- Disconnection Complete event will indicate that this command has been
+  -- completed.
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]  CommandHeader  header
+  $next [+2]         UInt           connection_handle
+    -- Connection_Handle (only the lower 12-bits are meaningful).
+    --   Range: 0x0000 to 0x0EFF
+
+  $next [+1]         StatusCode     reason
+    -- Reason for the disconnect.
+
+
+struct CreateConnectionCancelCommand:
+  -- Create Connection Cancel (v1.1) (BR/EDR)
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]               CommandHeader  header
+  $next [+BdAddr.$size_in_bytes]  BdAddr         bd_addr
+    -- BD_ADDR of the Create Connection Command request
+
+
+struct AcceptConnectionRequestCommand:
+  -- Accept Connection Request (v1.1) (BR/EDR)
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]               CommandHeader   header
+  $next [+BdAddr.$size_in_bytes]  BdAddr          bd_addr
+    -- The 48-bit BD_ADDR of the remote device requesting the connection.
+
+  $next [+1]                      ConnectionRole  role
+
+
+struct RejectConnectionRequestCommand:
+  -- Reject Connection Request (v1.1) (BR/EDR)
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]               CommandHeader  header
+  $next [+BdAddr.$size_in_bytes]  BdAddr         bd_addr
+    -- The 48-bit BD_ADDR of the remote device requesting the connection.
+
+  $next [+1]                      StatusCode     reason
+    -- Must be one of CONNECTION_REJECTED* from StatusCode in this file
+
+
+struct LinkKey:
+  0 [+16]  UInt:8[16]  value
+
+
+struct LinkKeyRequestReplyCommand:
+  -- Link Key Request Reply Command (v1.1) (BR/EDR)
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]               CommandHeader  header
+  $next [+BdAddr.$size_in_bytes]  BdAddr         bd_addr
+    -- The 48-bit BD_ADDR of the remote device requesting the connection.
+
+  let bredr_link_key_size = LinkKey.$size_in_bytes
+  $next [+bredr_link_key_size]    LinkKey        link_key
+    -- Link key to use for the connection with the peer device.
+
+
+struct LinkKeyRequestNegativeReplyCommand:
+  -- Link Key Request Negative Reply Command (v1.1) (BR/EDR)
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]               CommandHeader  header
+  $next [+BdAddr.$size_in_bytes]  BdAddr         bd_addr
+    -- BD_ADDR of the peer device that the host does not have a link key for.
+
+
+struct AuthenticationRequestedCommand:
+  -- Authentication Requested Command (v1.1) (BR/EDR)
+  --
+  -- NOTE on ReturnParams: No Command Complete event will be sent by the
+  -- Controller to indicate that this command has been completed. Instead, the
+  -- Authentication Complete event will indicate that this command has been
+  -- completed.
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]  CommandHeader  header
+  $next [+2]         UInt           connection_handle
+    -- Connection_Handle (only the lower 12-bits are meaningful).
+    --   Range: 0x0000 to 0x0EFF
+    -- Must be the handle of a connected ACL-U logical link.
+
+
+struct SetConnectionEncryptionCommand:
+  -- Set Connection Encryption Command (v1.1) (BR/EDR)
+  --
+  -- NOTE on ReturnParams: No Command Complete event will be sent by the
+  -- Controller to indicate that this command has been completed. Instead, the
+  -- Encryption Change event will indicate that this command has been completed.
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]  CommandHeader       header
+  $next [+2]         UInt                connection_handle
+    -- Connection_Handle (only the lower 12-bits are meaningful).
+    --   Range: 0x0000 to 0x0EFF
+    -- Must be the handle of a connected ACL-U logical link.
+
+  $next [+1]         GenericEnableParam  encryption_enable
+    -- Whether link level encryption should be turned on or off.
+
+
+struct RemoteNameRequestCommand:
+  -- Remote Name Request Command (v1.1) (BR/EDR)
+  --
+  -- NOTE on ReturnParams: No Command Complete event will be sent by the
+  -- Controller to indicate that this command has been completed. Instead, the
+  -- Remote Name Request Complete event will indicate that this command has been
+  -- completed.
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]               CommandHeader           header
+  $next [+BdAddr.$size_in_bytes]  BdAddr                  bd_addr
+    -- Address of the device whose name is to be requested.
+
+  $next [+1]                      PageScanRepetitionMode  page_scan_repetition_mode
+    -- Page Scan Repetition Mode of the device, obtained by Inquiry.
+
+  $next [+1]                      UInt                    reserved
+    [requires: this == 0]
+
+  $next [+2]                      ClockOffset             clock_offset
+    -- Clock offset.  The lower 15 bits of this represent bits 14-2
+    -- of CLKNPeripheral-CLK, and the highest bit is set when the other
+    -- bits are valid.
+
+
+struct ReadRemoteSupportedFeaturesCommand:
+  -- Read Remote Supported Features Command (v1.1) (BR/EDR)
+  --
+  -- NOTE on ReturnParams: No Command Complete event will be sent by the
+  -- Controller to indicate that this command has been completed. Instead, the
+  -- Read Remote Supported Features Complete event will indicate that this
+  -- command has been completed.
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]  CommandHeader  header
+  $next [+2]         UInt           connection_handle
+    -- Connection_Handle (only the lower 12-bits are meaningful).
+    --   Range: 0x0000 to 0x0EFF
+    -- Must be the handle of a connected ACL-U logical link.
+
+
+struct ReadRemoteExtendedFeaturesCommand:
+  -- Read Remote Extended Features Command (v1.2) (BR/EDR)
+  --
+  -- NOTE on ReturnParams: No Command Complete event will be sent by the
+  -- Controller to indicate that this command has been completed. Instead, the
+  -- Read Remote Extended Features Complete event will indicate that this
+  -- command has been completed.
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]  CommandHeader  header
+  $next [+2]         UInt           connection_handle
+    -- Connection_Handle (only the lower 12-bits are meaningful).
+    --   Range: 0x0000 to 0x0EFF
+    -- Must be the handle of a connected ACL-U logical link.
+
+  $next [+1]         UInt           page_number
+    -- Page of features to read.
+    -- Values:
+    --  - 0x00 standard features as if requested by Read Remote Supported Features
+    --  - 0x01-0xFF the corresponding features page (see Vol 2, Part C, Sec 3.3).
+
+
+struct ReadRemoteVersionInfoCommand:
+  -- Read Remote Version Information Command (v1.1) (BR/EDR & LE)
+  --
+  -- NOTE on ReturnParams: No Command Complete event will be sent by the
+  -- Controller to indicate that this command has been completed. Instead, the
+  -- Read Remote Version Information Complete event will indicate that this
+  -- command has been completed.
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]  CommandHeader  header
+  $next [+2]         UInt           connection_handle
+    -- Connection_Handle (only the lower 12-bits are meaningful).
+    --   Range: 0x0000 to 0x0EFF
+
+
+struct RejectSynchronousConnectionRequestCommand:
+  -- Reject Synchronous Connection Command (BR/EDR)
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]               CommandHeader  header
+  $next [+BdAddr.$size_in_bytes]  BdAddr         bd_addr
+    -- Address of the remote device that sent the request.
+
+  $next [+1]                      StatusCode     reason
+    -- Reason the connection request was rejected.
+
+
+struct IoCapabilityRequestReplyCommand:
+  -- IO Capability Request Reply Command (v2.1 + EDR) (BR/EDR)
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]               CommandHeader               header
+  $next [+BdAddr.$size_in_bytes]  BdAddr                      bd_addr
+    -- The BD_ADDR of the remote device involved in simple pairing process
+
+  $next [+1]                      IoCapability                io_capability
+    -- The IO capabilities of this device.
+
+  $next [+1]                      OobDataPresent              oob_data_present
+    -- Whether there is out-of-band data present, and what type.
+
+  $next [+1]                      AuthenticationRequirements  authentication_requirements
+    -- Authentication requirements of the host.
+
+
+struct UserConfirmationRequestReplyCommand:
+  -- User Confirmation Request Reply Command (v2.1 + EDR) (BR/EDR)
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]               CommandHeader  header
+  $next [+BdAddr.$size_in_bytes]  BdAddr         bd_addr
+    -- The BD_ADDR of the remote device involved in simple pairing process
+
+
+struct UserConfirmationRequestNegativeReplyCommand:
+  -- User Confirmation Request Negative Reply Command (v2.1 + EDR) (BR/EDR)
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]               CommandHeader  header
+  $next [+BdAddr.$size_in_bytes]  BdAddr         bd_addr
+    -- The BD_ADDR of the remote device involved in simple pairing process
+
+
+struct UserPasskeyRequestReplyCommand:
+  -- User Passkey Request Reply Command (v2.1 + EDR) (BR/EDR)
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]               CommandHeader  header
+  $next [+BdAddr.$size_in_bytes]  BdAddr         bd_addr
+    -- The BD_ADDR of the remote device involved in simple pairing process
+
+  $next [+4]                      UInt           numeric_value
+    -- Numeric value (passkey) entered by user.
+    [requires: 0 <= this <= 999999]
+
+
+struct SynchronousConnectionParameters:
+  -- Enhanced Setup Synchronous Connection Command (CSA2) (BR/EDR)
+
+  struct VendorCodingFormat:
+    0     [+1]  CodingFormat  coding_format
+    $next [+2]  UInt          company_id
+      -- See assigned numbers.
+
+    $next [+2]  UInt          vendor_codec_id
+      -- Shall be ignored if |coding_format| is not VENDOR_SPECIFIC.
+
+  enum ScoRetransmissionEffort:
+    [maximum_bits: 8]
+    NONE              = 0x00
+      -- SCO or eSCO
+
+    POWER_OPTIMIZED   = 0x01
+      -- eSCO only
+
+    QUALITY_OPTIMIZED = 0x02
+      -- eSCO only
+
+    DONT_CARE         = 0xFF
+      -- SCO or eSCO
+
+  0     [+4]         UInt                     transmit_bandwidth
+    -- Transmit bandwidth in octets per second.
+
+  $next [+4]         UInt                     receive_bandwidth
+    -- Receive bandwidth in octets per second.
+
+  let vcf_size = VendorCodingFormat.$size_in_bytes
+
+  $next [+vcf_size]  VendorCodingFormat       transmit_coding_format
+    -- Local Controller -> Remote Controller coding format.
+
+  $next [+vcf_size]  VendorCodingFormat       receive_coding_format
+    -- Remote Controller -> Local Controller coding format.
+
+  $next [+2]         UInt                     transmit_codec_frame_size_bytes
+
+  $next [+2]         UInt                     receive_codec_frame_size_bytes
+
+  $next [+4]         UInt                     input_bandwidth
+    -- Host->Controller data rate in octets per second.
+
+  $next [+4]         UInt                     output_bandwidth
+    -- Controller->Host data rate in octets per second.
+
+  $next [+vcf_size]  VendorCodingFormat       input_coding_format
+    -- Host->Controller coding format.
+
+  $next [+vcf_size]  VendorCodingFormat       output_coding_format
+    -- Controller->Host coding format.
+
+  $next [+2]         UInt                     input_coded_data_size_bits
+    -- Size, in bits, of the sample or framed data.
+
+  $next [+2]         UInt                     output_coded_data_size_bits
+    -- Size, in bits, of the sample or framed data.
+
+  $next [+1]         PcmDataFormat            input_pcm_data_format
+
+  $next [+1]         PcmDataFormat            output_pcm_data_format
+
+  $next [+1]         UInt                     input_pcm_sample_payload_msb_position
+    -- The number of bit positions within an audio sample that the MSB of
+    -- the sample is away from starting at the MSB of the data.
+
+  $next [+1]         UInt                     output_pcm_sample_payload_msb_position
+    -- The number of bit positions within an audio sample that the MSB of
+    -- the sample is away from starting at the MSB of the data.
+
+  $next [+1]         ScoDataPath              input_data_path
+
+  $next [+1]         ScoDataPath              output_data_path
+
+  $next [+1]         UInt                     input_transport_unit_size_bits
+    -- The number of bits in each unit of data received from the Host over the audio data transport.
+    -- 0 indicates "not applicable"  (implied by the choice of audio data transport).
+
+  $next [+1]         UInt                     output_transport_unit_size_bits
+    -- The number of bits in each unit of data sent to the Host over the audio data transport.
+    -- 0 indicates "not applicable"  (implied by the choice of audio data transport).
+
+  $next [+2]         UInt                     max_latency_ms
+    -- The value in milliseconds representing the upper limit of the sum of
+    -- the synchronous interval, and the size of the eSCO window, where the
+    -- eSCO window is the reserved slots plus the retransmission window.
+    -- Minimum: 0x0004
+    -- Don't care: 0xFFFF
+
+  $next [+2]         ScoPacketType            packet_types
+    -- Bitmask of allowed packet types.
+
+  $next [+1]         ScoRetransmissionEffort  retransmission_effort
+
+
+struct EnhancedSetupSynchronousConnectionCommand:
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]  CommandHeader                    header
+  $next [+2]         UInt                             connection_handle
+    -- The connection handle of the associated ACL link if creating a new (e)SCO connection, or the
+    -- handle of an existing eSCO link if updating connection parameters.
+
+  let scp_size = SynchronousConnectionParameters.$size_in_bytes
+  $next [+scp_size]  SynchronousConnectionParameters  connection_parameters
+
+
+struct EnhancedAcceptSynchronousConnectionRequestCommand:
+  -- Enhanced Accept Synchronous Connection Request Command (CSA2) (BR/EDR)
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]               CommandHeader                    header
+  $next [+BdAddr.$size_in_bytes]  BdAddr                           bd_addr
+    -- The 48-bit BD_ADDR of the remote device requesting the connection.
+
+  let scp_size = SynchronousConnectionParameters.$size_in_bytes
+  $next [+scp_size]               SynchronousConnectionParameters  connection_parameters
+
+
+struct SetEventMaskCommand:
+  -- Set Event Mask Command (v1.1)
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]  CommandHeader  header
+  $next [+8]         UInt           event_mask
+    -- 64-bit Bit mask used to control which HCI events are generated by the HCI for the
+    -- Host. See enum class EventMask in hci_constants.h
+
+
+struct WriteLocalNameCommand:
+  -- Write Local Name Command (v1.1) (BR/EDR)
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]         CommandHeader  header
+  let local_name_size = LocalName.$size_in_bytes
+  $next [+local_name_size]  LocalName      local_name
+    -- A UTF-8 encoded User Friendly Descriptive Name for the device.
+    -- If the name contained in the parameter is shorter than 248 octets, the end
+    -- of the name is indicated by a NULL octet (0x00), and the following octets
+    -- (to fill up 248 octets, which is the length of the parameter) do not have
+    -- valid values.
+
+
+struct WritePageTimeoutCommand:
+  -- Write Page Timeout Command (v1.1) (BR/EDR)
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]  CommandHeader  header
+  $next [+2]         UInt           page_timeout
+    -- Page_Timeout, in time slices (0.625 ms)
+    -- Range: From MIN to MAX in PageTimeout in this file
+    [requires: 0x0001 <= this <= 0xFFFF]
+
+
+struct WriteScanEnableCommand:
+  -- Write Scan Enable Command (v1.1) (BR/EDR)
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]  CommandHeader   header
+  $next [+1]         ScanEnableBits  scan_enable
+    -- Bit Mask of enabled scans. See enum class ScanEnableBits in this file
+    -- for how to construct this bitfield.
+
+
+struct WritePageScanActivityCommand:
+  -- Write Page Scan Activity Command (v1.1) (BR/EDR)
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]  CommandHeader  header
+  $next [+2]         UInt           page_scan_interval
+    -- Page_Scan_Interval, in time slices (0.625ms)
+    -- Valid Range: MIN - MAX in ScanInterval in this file
+    [requires: 0x0012 <= this <= 0x1000]
+
+  $next [+2]         UInt           page_scan_window
+    -- Page_Scan_Window, in time slices
+    -- Valid Range: MIN - MAX in ScanWindow in this file
+    [requires: 0x0011 <= this <= 0x1000]
+
+
+struct WriteInquiryScanActivityCommand:
+  -- Write Inquiry Scan Activity Command (v1.1) (BR/EDR)
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]  CommandHeader  header
+  $next [+2]         UInt           inquiry_scan_interval
+    -- Inquiry_Scan_Interval, in time slices (0.625ms)
+    -- Valid Range: MIN - MAX in ScanInterval in this file
+    [requires: 0x0012 <= this <= 0x1000]
+
+  $next [+2]         UInt           inquiry_scan_window
+    -- Inquiry_Scan_Window, in time slices
+    -- Valid Range: MIN - MAX in ScanWindow in this file
+    [requires: 0x0011 <= this <= 0x1000]
+
+
+struct WriteAutomaticFlushTimeoutCommand:
+  -- Write Automatic Flush Timeout Command (v1.1) (BR/EDR)
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]  CommandHeader  header
+  $next [+2]         UInt           connection_handle
+    -- Connection_Handle (only the lower 12-bits are meaningful).
+    --   Range: 0x0000 to 0x0EFF
+    [requires: 0x0000 <= this <= 0x0EFF]
+
+  $next [+2]         UInt           flush_timeout
+    -- The value for the Flush_Timeout configuration parameter (Core Spec v5.2, Vol 4, Part E, Sec 6.19).
+    -- Range: 0x0000 to 0x07FF. 0x0000 indicates infinite flush timeout (no automatic flush).
+    -- Time = flush_timeout * 0.625ms.
+    -- Time Range: 0.625ms to 1279.375ms.
+    [requires: 0x0000 <= this <= 0x07FF]
+
+
+struct WriteSynchronousFlowControlEnableCommand:
+  -- Write Synchonous Flow Control Enable Command (BR/EDR)
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]  CommandHeader       header
+  $next [+1]         GenericEnableParam  synchronous_flow_control_enable
+    -- If enabled, HCI_Number_Of_Completed_Packets events shall be sent from the controller
+    -- for synchronous connection handles.
+
+
+struct WriteInquiryScanTypeCommand:
+  -- Write Inquiry Scan Type (v1.2) (BR/EDR)
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]  CommandHeader    header
+  $next [+1]         InquiryScanType  inquiry_scan_type
+    -- See enum class InquiryScanType in this file for possible values
+
+
+struct WriteExtendedInquiryResponseCommand:
+  -- Write Extended Inquiry Response (v1.2) (BR/EDR)
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]  CommandHeader            header
+  $next [+1]         UInt                     fec_required
+    -- If FEC Encoding is required. (v1.2) (7.3.56)
+
+  let eir_size = ExtendedInquiryResponse.$size_in_bytes
+  $next [+eir_size]  ExtendedInquiryResponse  extended_inquiry_response
+    -- Extended inquiry response data as defined in Vol 3, Part C, Sec 8
+
+
+struct WriteSimplePairingModeCommand:
+  -- Write Simple Pairing Mode (v2.1 + EDR) (BR/EDR)
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]  CommandHeader       header
+  $next [+1]         GenericEnableParam  simple_pairing_mode
+
+
+struct LESetExtendedScanEnableCommand:
+  -- LE Set Extended Scan Enable Command (v5.0) (LE)
+
+  let hdr_size = CommandHeader.$size_in_bytes
+
+  0     [+hdr_size]  CommandHeader                       header
+
+  $next [+1]         GenericEnableParam                  scanning_enabled
+
+  $next [+1]         LEExtendedDuplicateFilteringOption  filter_duplicates
+    -- See enum class LEExtendedDuplicateFilteringOption in this file for possible values
+
+  $next [+2]         UInt                                duration
+    -- Possible values:
+    --   0x0000: Scan continuously until explicitly disabled
+    --   0x0001-0xFFFF: Scan duration, where:
+    --     Time = N * 10 ms
+    --     Time Range: 10 ms to 655.35 s
+
+  $next [+2]         UInt                                period
+    -- Possible values:
+    --   0x0000: Periodic scanning disabled (scan continuously)
+    --   0x0001-0xFFFF: Time interval from when the Controller started its last
+    --   Scan_Duration until it begins the subsequent Scan_Duration, where:
+    --     Time = N * 1.28 sec
+    --     Time Range: 1.28 s to 83,884.8 s
+
+# ========================= HCI Event packets ===========================
+# Core Spec v5.3 Vol 4, Part E, Section 7.7
+
+
+struct VendorDebugEvent:
+  -- This opcode is reserved for vendor-specific debugging events.
+  -- See Core Spec v5.3 Vol 4, Part E, Section 5.4.4.
+  let hdr_size = EventHeader.$size_in_bytes
+  0     [+hdr_size]  EventHeader  header
+  $next [+1]         UInt         subevent_code
+    -- The event code for the vendor subevent.
+
+
+struct InquiryCompleteEvent:
+  -- Inquiry Complete Event (v1.1) (BR/EDR)
+  let hdr_size = EventHeader.$size_in_bytes
+  0     [+hdr_size]  EventHeader  header
+  $next [+1]         StatusCode   status
+
+
+struct CommandCompleteEvent:
+  -- Core Spec v5.3 Vol 4, Part E, Section 7.7.14
+  -- EventHeader.opcode == 0xe
+  let hdr_size = EventHeader.$size_in_bytes
+  0     [+hdr_size]  EventHeader  header
+  $next [+1]         UInt         num_hci_command_packets
+  $next [+2]         OpCodeBits   command_opcode
+  let event_fixed_size = $size_in_bytes-hdr_size
+  let return_parameters_size = header.parameter_total_size-event_fixed_size
+
+# ============================ Test packets =============================
+
+
+struct TestCommandPacket:
+  -- Test HCI Command packet with single byte payload.
+  let hdr_size = CommandHeader.$size_in_bytes
+  0     [+hdr_size]  CommandHeader  header
+  $next [+1]         UInt           payload
+
+
+struct TestEventPacket:
+  -- Test HCI Event packet with single byte payload.
+  let hdr_size = EventHeader.$size_in_bytes
+  0     [+hdr_size]  EventHeader  header
+  $next [+1]         UInt         payload
diff --git a/pw_bluetooth/public/pw_bluetooth/vendor.emb b/pw_bluetooth/public/pw_bluetooth/vendor.emb
new file mode 100644
index 0000000..d528972
--- /dev/null
+++ b/pw_bluetooth/public/pw_bluetooth/vendor.emb
@@ -0,0 +1,46 @@
+# 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.
+
+# This file contains Emboss packet definitions for extensions to the Bluetooth
+# Host-Controller interface. These extensions are not standardized through the
+# Bluetooth SIG.
+#
+# NOTE: The definitions below are incomplete. They get added as needed.
+# This list will grow as we support more vendor features.
+
+import "hci.emb" as hci
+
+[$default byte_order: "LittleEndian"]
+[(cpp) namespace: "pw::bluetooth::emboss"]
+
+# ======================= Android HCI extensions ========================
+# Documentation: https://source.android.com/devices/bluetooth/hci_requirements
+
+
+# ============ Events ============
+
+
+struct LEMultiAdvtStateChangeSubevent:
+  -- LE multi-advertising state change subevent.
+  0     [+hci.VendorDebugEvent.$size_in_bytes]  hci.VendorDebugEvent  vendor_event
+  $next [+1]                                    UInt                  advertising_handle
+    -- Handle used to identify an advertising set.
+
+  $next [+1]                                    hci.StatusCode        status
+    -- Reason for state change. Currently will always be 0x00.
+    -- 0x00: Connection received.
+
+  $next [+2]                                    UInt                  connection_handle
+    -- Handle used to identify the connection that caused the state change (i.e.
+    -- advertising instance to be disabled). Value will be 0xFFFF if invalid.
diff --git a/third_party/emboss/BUILD.gn b/third_party/emboss/BUILD.gn
index 2413281..12f733a 100644
--- a/third_party/emboss/BUILD.gn
+++ b/third_party/emboss/BUILD.gn
@@ -13,6 +13,7 @@
 # the License.
 
 import("//build_overrides/pigweed.gni")
+import("$dir_pw_build/python.gni")
 
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_third_party/emboss/emboss.gni")
@@ -37,8 +38,12 @@
   ]
 }
 
-group("runner") {
-  deps = [ "embossc_runner.py" ]
+# Exists solely to satisfy presubmit. embossc_runner.py is used for real in
+# build_defs.gni.
+action("embossc_runner") {
+  script = "embossc_runner.py"
+  visibility = []
+  outputs = [ "$target_gen_dir/foo" ]
 }
 
 pw_doc_group("docs") {
diff --git a/third_party/emboss/build_defs.gni b/third_party/emboss/build_defs.gni
index 65f4b01..229d1e0 100644
--- a/third_party/emboss/build_defs.gni
+++ b/third_party/emboss/build_defs.gni
@@ -13,33 +13,53 @@
 # the License.
 
 import("//build_overrides/pigweed.gni")
-
+import("$dir_pw_build/python_action.gni")
 import("$dir_pw_third_party/emboss/emboss.gni")
 
+# Compiles a .emb file into a .h file.
+# $dir_pw_third_party_emboss must be set to the path of your Emboss
+# installation.
+#
+# Parameters
+#
+#   source (required)
+#     [path] The path to the .emb file that is to be compiled.
+#
+#   import_dirs (optional)
+#     [list of paths] The list of directories to search for imported .emb files,
+#     in the order they should be searched. The directory that `source` is in is
+#     always searched first.
+#
+#   imports (optional)
+#     [list of paths] Paths to any imported .emb files, including those imported
+#     resursively.
+#
+#   deps (optional)
+#     [list of targets] Paths to other emboss_cc_library targets that are
+#     imported by this target.
 template("emboss_cc_library") {
   assert(defined(invoker.source), "Need source arg for emboss_cc_library")
   assert(
       "$dir_pw_third_party_emboss" != "",
       "\$dir_pw_third_party_emboss must be set to the path of your Emboss installation")
-  source = get_path_info(invoker.source, "file")
 
   # The --output-path arg to the embossc script only specifies the
   # prefix of the path at which the generated header is placed. To this
   # prefix, the script appends the entire path of the input Emboss source,
-  # which is provided as rebase_path(source, root_build_dir).
+  # which is provided as rebase_path(invoker.source, root_build_dir).
 
-  # rebase_path(source, root_build_dir) will always start with a number
+  # rebase_path(invoker.source, root_build_dir) will always start with a number
   # of updirs (e.g. "../../../").
 
-  # In order for the compiled header path in the embossc script to resolve
-  # to $target_gen_dir as we desire, we must provide an --output-path arg
-  # that resolves to $target_gen_dir after rebase_path(source, root_build_dir)
-  # is appended to it. To achieve this, we specify output-path to be
-  # $root_gen_dir followed by a number of fake directories needed to cancel
-  # out these starting updirs.
-  compiled_header_path = "$target_gen_dir/" + source + ".h"
+  # In order for the compiled header path in the embossc script to resolve to
+  # $target_gen_dir as we desire, we must provide an --output-path arg that
+  # resolves to $target_gen_dir after rebase_path(invoker.source,
+  # root_build_dir) is appended to it. To achieve this, we specify output-path
+  # to be $root_gen_dir followed by a number of fake directories needed to
+  # cancel out these starting updirs.
+  compiled_header_path = "$target_gen_dir/" + invoker.source + ".h"
   path_sep = "/"
-  elements = string_split(rebase_path(source, root_build_dir), path_sep)
+  elements = string_split(rebase_path(invoker.source, root_build_dir), path_sep)
   updirs = filter_include(elements, [ ".." ])
 
   fakedirs = []
@@ -48,19 +68,34 @@
   }
   output_path = root_gen_dir + path_sep + string_join(path_sep, fakedirs)
 
-  action(target_name + "_header") {
+  # Look for imports in the same directory as invoker.source by default.
+  default_import_dir = get_path_info(invoker.source, "abspath")
+  default_import_dir = get_path_info(default_import_dir, "dir")
+  default_import_dir = rebase_path(default_import_dir, root_build_dir)
+
+  pw_python_action(target_name + "_header") {
     script = "$dir_pw_third_party/emboss/embossc_runner.py"
+
     args = [
       rebase_path("$dir_pw_third_party_emboss/embossc", root_build_dir),
       "--generate",
       "cc",
+      "--import-dir",
+      default_import_dir,
       "--output-path",
       rebase_path(output_path, root_build_dir),
-      rebase_path(source, root_build_dir),
+      rebase_path(invoker.source, root_build_dir),
     ]
-    inputs = [
-      source,
-      "$dir_pw_third_party_emboss/embossc",
+    if (defined(invoker.import_dirs)) {
+      foreach(dir, invoker.import_dirs) {
+        args += [
+          "--import-dir",
+          dir,
+        ]
+      }
+    }
+
+    sources = [
       "$dir_pw_third_party_emboss/compiler/back_end/__init__.py",
       "$dir_pw_third_party_emboss/compiler/back_end/cpp/__init__.py",
       "$dir_pw_third_party_emboss/compiler/back_end/cpp/emboss_codegen_cpp.py",
@@ -95,11 +130,19 @@
       "$dir_pw_third_party_emboss/compiler/util/parser_types.py",
       "$dir_pw_third_party_emboss/compiler/util/simple_memoizer.py",
       "$dir_pw_third_party_emboss/compiler/util/traverse_ir.py",
+      "$dir_pw_third_party_emboss/embossc",
+      invoker.source,
     ]
+
+    # TODO(benlawson): Use a depfile for adding imports to inputs (https://github.com/google/emboss/issues/68).
+    if (defined(invoker.imports)) {
+      inputs = invoker.imports
+    }
+
     outputs = [ compiled_header_path ]
   }
 
-  config("emboss_config") {
+  config(target_name + "_emboss_config") {
     include_dirs = [
       "$dir_pw_third_party_emboss",
       root_gen_dir,
@@ -107,9 +150,19 @@
   }
 
   source_set(target_name) {
+    forward_variables_from(invoker, "*")
+
     sources = [ compiled_header_path ]
-    deps = [ "$dir_pw_third_party/emboss:cpp_utils" ]
+
+    if (!defined(invoker.deps)) {
+      deps = []
+    }
+    deps += [ "$dir_pw_third_party/emboss:cpp_utils" ]
     public_deps = [ ":" + target_name + "_header" ]
-    public_configs = [ ":emboss_config" ]
+
+    if (!defined(invoker.public_configs)) {
+      public_configs = []
+    }
+    public_configs += [ ":" + target_name + "_emboss_config" ]
   }
 }