Merge "pdl: fix computation of `SourceLocation` line numbers"
diff --git a/android/app/src/com/android/bluetooth/btservice/storage/DatabaseManager.java b/android/app/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
index 81475bb..7ee881f 100644
--- a/android/app/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
+++ b/android/app/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
@@ -377,7 +377,8 @@
             Metadata data = mMetadataCache.get(address);
             int connectionPolicy = data.getProfileConnectionPolicy(profile);
 
-            Log.v(TAG, "getProfileConnectionPolicy: " + address + ", profile=" + profile
+            Log.v(TAG, "getProfileConnectionPolicy: " + address + ", profile="
+                    + BluetoothProfile.getProfileName(profile)
                     + ", connectionPolicy = " + connectionPolicy);
             return connectionPolicy;
         }
diff --git a/floss/build/Dockerfile b/floss/build/Dockerfile
index 7e938f9..b31e1f4 100644
--- a/floss/build/Dockerfile
+++ b/floss/build/Dockerfile
@@ -5,7 +5,8 @@
 
 # Inherit from a recent Debian version. The slim version is a smaller variant
 # meant for containers.
-FROM debian:bookworm-slim
+# This digest is taken from the tag debian:bookworm-slim (if you want to update)
+FROM debian@sha256:b66f66d473ef3128436ba2812198edcae86c268eb530dff44ff6ae26f9a2ee30
 
 # First install all required apt packages.
 RUN apt-get update && \
@@ -56,8 +57,10 @@
     zlib1g-dev \
     ;
 
-# Next install the Rust toolchain.
-RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
+# Next install the Rust toolchain. Download the toolchain to the local folder
+# using curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs
+ADD rustup/rustup.sh /tmp
+RUN /tmp/rustup.sh -y --default-toolchain 1.59.0
 
 # Add .cargo/bin to $PATH
 ENV PATH="/root/.cargo/bin:${PATH}"
diff --git a/floss/build/README.md b/floss/build/README.md
index ac58410..1697a39 100644
--- a/floss/build/README.md
+++ b/floss/build/README.md
@@ -1,13 +1,13 @@
-# Docker build for Floss
+# Podman build for Floss
 
-This repo contains the Docker image build rule, used to generate the docker
-image necessary to build Floss. If building a new docker image, run
-`docker-build-image.py` with the tag `floss:latest`.
+This repo contains the Podman image build rule, used to generate the Podman
+image necessary to build Floss. If building a new Podman image, run
+`podman-build-image.py` with the tag `floss:latest`.
 
-## Using the docker image to build
+## Using the podman image to build
 
-Once the Docker image is built (and assuming it's tagged as `floss:latest`), you
-should use the `build-in-docker.py` script to build the current repo.
+Once the Podman image is built (and assuming it's tagged as `floss:latest`), you
+should use the `build-in-podman.py` script to build the current repo.
 
 This script will use the local `floss:latest` (or pull it from the registry),
 mount (or create) the `floss-out` volume to `/root/.floss` and the current
@@ -18,6 +18,6 @@
 * `./build.py --libdir=/usr/lib/x86-64_linux_gnu/`
 
 If you want to run the build more quickly (or pass other commands), run
-`build-in-docker.py --only-start`. This will only start the container for you
+`build-in-podman.py --only-start`. This will only start the container for you
 (doing the correct mounts) and will print the commands it would have run via
-docker exec normally.
+`podman exec` normally.
diff --git a/floss/build/build-in-docker.py b/floss/build/build-in-docker.py
deleted file mode 100755
index d78d404..0000000
--- a/floss/build/build-in-docker.py
+++ /dev/null
@@ -1,164 +0,0 @@
-#!/usr/bin/env python3
-
-import argparse
-import os
-import subprocess
-import sys
-import time
-
-
-class FlossDockerRunner:
-    """Runs Floss build inside docker container."""
-
-    # Commands to run for build
-    BUILD_COMMANDS = [
-        # First run bootstrap to get latest code + create symlinks
-        ['/root/src/build.py', '--run-bootstrap'],
-
-        # Clean up any previous artifacts inside the volume
-        ['/root/src/build.py', '--target', 'clean'],
-
-        # Run normal code builder
-        ['/root/src/build.py', '--target', 'all'],
-
-        # Run tests
-        ['/root/src/build.py', '--target', 'test'],
-    ]
-
-    def __init__(self, workdir, rootdir, image_tag, volume_tag):
-        """ Constructor.
-
-        Args:
-            workdir: Current working directory (should be the script path).
-            rootdir: Root directory for Bluetooth.
-            build_tag: Tag for docker image used for building.
-        """
-        self.workdir = workdir
-        self.rootdir = rootdir
-        self.image_tag = image_tag
-        self.env = os.environ.copy()
-
-        # Name of running container
-        self.container_name = 'floss-docker-runner'
-
-        # Name of volume where we'll send build output
-        self.volume_name = volume_tag
-
-    def run_command(self, target, args, cwd=None, env=None, ignore_rc=False):
-        """ Run command and stream the output.
-        """
-        # Set some defaults
-        if not cwd:
-            cwd = self.workdir
-        if not env:
-            env = self.env
-
-        rc = 0
-        process = subprocess.Popen(args, cwd=cwd, env=env, stdout=subprocess.PIPE)
-        while True:
-            line = process.stdout.readline()
-            print(line.decode('utf-8'), end="")
-            if not line:
-                rc = process.poll()
-                if rc is not None:
-                    break
-
-                time.sleep(0.1)
-
-        if rc != 0 and not ignore_rc:
-            raise Exception("{} failed. Return code is {}".format(target, rc))
-
-    def _create_volume_if_needed(self):
-        # Check if the volume exists. Otherwise create it.
-        try:
-            subprocess.check_output(['docker', 'volume', 'inspect', self.volume_name])
-        finally:
-            self.run_command('docker volume create', ['docker', 'volume', 'create', self.volume_name])
-
-    def start_container(self):
-        """Starts the docker container with correct mounts."""
-        # Stop any previously started container.
-        self.stop_container(ignore_error=True)
-
-        # Create volume and create mount string
-        self._create_volume_if_needed()
-        mount_output_volume = 'type=volume,src={},dst=/root/.floss'.format(self.volume_name)
-
-        # Mount the source directory
-        mount_src_dir = 'type=bind,src={},dst=/root/src'.format(self.rootdir)
-
-        # Run the docker image. It will run `tail` indefinitely so the container
-        # doesn't close and we can run `docker exec` on it.
-        self.run_command('docker run', [
-            'docker', 'run', '--name', self.container_name, '--mount', mount_output_volume, '--mount', mount_src_dir,
-            '-d', self.image_tag, 'tail', '-f', '/dev/null'
-        ])
-
-    def stop_container(self, ignore_error=False):
-        """Stops the docker container for build."""
-        self.run_command('docker stop', ['docker', 'stop', '-t', '1', self.container_name], ignore_rc=ignore_error)
-        self.run_command('docker rm', ['docker', 'rm', self.container_name], ignore_rc=ignore_error)
-
-    def do_build(self):
-        """Runs the basic build commands."""
-        # Start container before building
-        self.start_container()
-
-        # Run all commands
-        for i, cmd in enumerate(self.BUILD_COMMANDS):
-            self.run_command('docker exec #{}'.format(i), ['docker', 'exec', '-it', self.container_name] + cmd)
-
-        # Stop container before exiting
-        self.stop_container()
-
-    def print_do_build(self):
-        """Prints the commands for building."""
-        docker_exec = ['docker', 'exec', '-it', self.container_name]
-        print('Normally, build would run the following commands: \n')
-        for cmd in self.BUILD_COMMANDS:
-            print(' '.join(docker_exec + cmd))
-
-    def check_docker_runnable(self):
-        try:
-            subprocess.check_output(['docker', 'ps'], stderr=subprocess.STDOUT)
-        except subprocess.CalledProcessError as err:
-            if 'denied' in err.output.decode('utf-8'):
-                print('Run script as sudo')
-            else:
-                print('Unexpected error: {}'.format(err.output.decode('utf-8')))
-
-            return False
-
-        # No exception means docker is ok
-        return True
-
-
-if __name__ == "__main__":
-    parser = argparse.ArgumentParser('Builder Floss inside docker image.')
-    parser.add_argument(
-        '--only-start',
-        action='store_true',
-        default=False,
-        help='Only start the container. Prints the commands it would have ran.')
-    parser.add_argument('--only-stop', action='store_true', default=False, help='Only stop the container and exit.')
-    parser.add_argument('--image-tag', default='floss:latest', help='Docker image to use to build.')
-    parser.add_argument('--volume-tag', default='floss-out', help='Name of volume to use.')
-    args = parser.parse_args()
-
-    # cwd should be set to same directory as this script (that's where Dockerfile
-    # is kept).
-    workdir = os.path.dirname(os.path.abspath(sys.argv[0]))
-    rootdir = os.path.abspath(os.path.join(workdir, '../..'))
-
-    fdr = FlossDockerRunner(workdir, rootdir, args.image_tag, args.volume_tag)
-
-    # Make sure docker is runnable before continuing
-    if fdr.check_docker_runnable():
-        # Handle some flags
-        if args.only_start:
-            fdr.start_container()
-            fdr.print_do_build()
-        elif args.only_stop:
-            fdr.stop_container()
-        else:
-            fdr.do_build()
diff --git a/floss/build/build-in-podman.py b/floss/build/build-in-podman.py
new file mode 100755
index 0000000..ba45914
--- /dev/null
+++ b/floss/build/build-in-podman.py
@@ -0,0 +1,188 @@
+#!/usr/bin/env python3
+
+import argparse
+import os
+import subprocess
+import sys
+import time
+
+SRC_MOUNT = "/root/src"
+STAGING_MOUNT = "/root/.floss"
+
+
+class FlossPodmanRunner:
+    """Runs Floss build inside podman container."""
+
+    # Commands to run for build
+    BUILD_COMMANDS = [
+        # First run bootstrap to get latest code + create symlinks
+        [f'{SRC_MOUNT}/build.py', '--run-bootstrap'],
+
+        # Clean up any previous artifacts inside the volume
+        [f'{SRC_MOUNT}/build.py', '--target', 'clean'],
+
+        # Run normal code builder
+        [f'{SRC_MOUNT}/build.py', '--target', 'all'],
+
+        # Run tests
+        [f'{SRC_MOUNT}/build.py', '--target', 'test'],
+    ]
+
+    def __init__(self, workdir, rootdir, image_tag, volume_name, container_name, staging_dir):
+        """ Constructor.
+
+        Args:
+            workdir: Current working directory (should be the script path).
+            rootdir: Root directory for Bluetooth.
+            image_tag: Tag for podman image used for building.
+            volume_name: Volume name used for storing artifacts.
+            container_name: Name for running container instance.
+            staging_dir: Directory to mount for artifacts instead of using volume.
+        """
+        self.workdir = workdir
+        self.rootdir = rootdir
+        self.image_tag = image_tag
+        self.env = os.environ.copy()
+
+        # Name of running container
+        self.container_name = container_name
+
+        # Name of volume to write output.
+        self.volume_name = volume_name
+        # Staging dir where we send output instead of the volume.
+        self.staging_dir = staging_dir
+
+    def run_command(self, target, args, cwd=None, env=None, ignore_rc=False):
+        """ Run command and stream the output.
+        """
+        # Set some defaults
+        if not cwd:
+            cwd = self.workdir
+        if not env:
+            env = self.env
+
+        rc = 0
+        process = subprocess.Popen(args, cwd=cwd, env=env, stdout=subprocess.PIPE)
+        while True:
+            line = process.stdout.readline()
+            print(line.decode('utf-8'), end="")
+            if not line:
+                rc = process.poll()
+                if rc is not None:
+                    break
+
+                time.sleep(0.1)
+
+        if rc != 0 and not ignore_rc:
+            raise Exception("{} failed. Return code is {}".format(target, rc))
+
+    def _create_volume_if_needed(self):
+        # Check if the volume exists. Otherwise create it.
+        try:
+            subprocess.check_output(['podman', 'volume', 'inspect', self.volume_name])
+        except:
+            self.run_command('podman volume create', ['podman', 'volume', 'create', self.volume_name])
+
+    def start_container(self):
+        """Starts the podman container with correct mounts."""
+        # Stop any previously started container.
+        self.stop_container(ignore_error=True)
+
+        # Create volume and create mount string
+        if self.staging_dir:
+            mount_output_volume = 'type=bind,src={},dst={}'.format(self.staging_dir, STAGING_MOUNT)
+        else:
+            # If not using staging dir, use the volume instead
+            self._create_volume_if_needed()
+            mount_output_volume = 'type=volume,src={},dst={}'.format(self.volume_name, STAGING_MOUNT)
+
+        # Mount the source directory
+        mount_src_dir = 'type=bind,src={},dst={}'.format(self.rootdir, SRC_MOUNT)
+
+        # Run the podman image. It will run `tail` indefinitely so the container
+        # doesn't close and we can run `podman exec` on it.
+        self.run_command('podman run', [
+            'podman', 'run', '--name', self.container_name, '--mount', mount_output_volume, '--mount', mount_src_dir,
+            '-d', self.image_tag, 'tail', '-f', '/dev/null'
+        ])
+
+    def stop_container(self, ignore_error=False):
+        """Stops the podman container for build."""
+        self.run_command('podman stop', ['podman', 'stop', '-t', '1', self.container_name], ignore_rc=ignore_error)
+        self.run_command('podman rm', ['podman', 'rm', self.container_name], ignore_rc=ignore_error)
+
+    def do_build(self):
+        """Runs the basic build commands."""
+        # Start container before building
+        self.start_container()
+
+        try:
+            # Run all commands
+            for i, cmd in enumerate(self.BUILD_COMMANDS):
+                self.run_command('podman exec #{}'.format(i), ['podman', 'exec', '-it', self.container_name] + cmd)
+        finally:
+            # Always stop container before exiting
+            self.stop_container()
+
+    def print_do_build(self):
+        """Prints the commands for building."""
+        podman_exec = ['podman', 'exec', '-it', self.container_name]
+        print('Normally, build would run the following commands: \n')
+        for cmd in self.BUILD_COMMANDS:
+            print(' '.join(podman_exec + cmd))
+
+    def check_podman_runnable(self):
+        try:
+            subprocess.check_output(['podman', 'ps'], stderr=subprocess.STDOUT)
+        except subprocess.CalledProcessError as err:
+            if 'denied' in err.output.decode('utf-8'):
+                print('Run script as sudo')
+            else:
+                print('Unexpected error: {}'.format(err.output.decode('utf-8')))
+
+            return False
+
+        # No exception means podman is ok
+        return True
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser('Builder Floss inside podman image.')
+    parser.add_argument(
+        '--only-start',
+        action='store_true',
+        default=False,
+        help='Only start the container. Prints the commands it would have ran.')
+    parser.add_argument('--only-stop', action='store_true', default=False, help='Only stop the container and exit.')
+    parser.add_argument('--image-tag', default='floss:latest', help='Podman image to use to build.')
+    parser.add_argument(
+        '--volume-tag',
+        default='floss-out',
+        help='Name of volume to use. This is where build artifacts will be stored by default.')
+    parser.add_argument(
+        '--staging-dir',
+        default=None,
+        help='Staging directory to use instead of volume. Build artifacts will be written here.')
+    parser.add_argument('--container-name', default='floss-podman-runner', help='What to name the started container')
+    args = parser.parse_args()
+
+    # cwd should be set to same directory as this script (that's where Podmanfile
+    # is kept).
+    workdir = os.path.dirname(os.path.abspath(sys.argv[0]))
+    rootdir = os.path.abspath(os.path.join(workdir, '../..'))
+
+    # Determine staging directory absolute path
+    staging = os.path.abspath(args.staging_dir) if args.staging_dir else None
+
+    fdr = FlossPodmanRunner(workdir, rootdir, args.image_tag, args.volume_tag, args.container_name, staging)
+
+    # Make sure podman is runnable before continuing
+    if fdr.check_podman_runnable():
+        # Handle some flags
+        if args.only_start:
+            fdr.start_container()
+            fdr.print_do_build()
+        elif args.only_stop:
+            fdr.stop_container()
+        else:
+            fdr.do_build()
diff --git a/floss/build/docker-build-image.py b/floss/build/podman-build-image.py
similarity index 65%
rename from floss/build/docker-build-image.py
rename to floss/build/podman-build-image.py
index 81b622b..af772c2 100755
--- a/floss/build/docker-build-image.py
+++ b/floss/build/podman-build-image.py
@@ -8,8 +8,8 @@
 SRC_MOUNT = "/root/src"
 
 
-class DockerImageBuilder:
-    """Builds the docker image for Floss build environment."""
+class PodmanImageBuilder:
+    """Builds the podman image for Floss build environment."""
 
     def __init__(self, workdir, rootdir, tag):
         """ Constructor.
@@ -27,7 +27,7 @@
         self.final_tag = tag
         self.env = os.environ.copy()
 
-        # Mark dpkg builders for docker
+        # Mark dpkg builders for podman
         self.env['LIBCHROME_DOCKER'] = '1'
         self.env['MODP_DOCKER'] = '1'
 
@@ -55,19 +55,19 @@
         if rc != 0 and not ignore_rc:
             raise Exception("{} failed. Return code is {}".format(target, rc))
 
-    def _docker_build(self):
-        self.run_command('docker build', ['docker', 'build', '-t', self.build_tag, '.'])
+    def _podman_build(self):
+        self.run_command('podman build', ['podman', 'build', '-t', self.build_tag, '.'])
 
     def _build_dpkg_and_commit(self):
         # Try to remove any previous instance of the container that may be
         # running if this script didn't complete cleanly last time.
-        self.run_command('docker stop', ['docker', 'stop', '-t', '1', self.container_name], ignore_rc=True)
-        self.run_command('docker rm', ['docker', 'rm', self.container_name], ignore_rc=True)
+        self.run_command('podman stop', ['podman', 'stop', '-t', '1', self.container_name], ignore_rc=True)
+        self.run_command('podman rm', ['podman', 'rm', self.container_name], ignore_rc=True)
 
         # Runs never terminating application on the newly built image in detached mode
         mount_str = 'type=bind,src={},dst={},readonly'.format(self.rootdir, SRC_MOUNT)
-        self.run_command('docker run', [
-            'docker', 'run', '--name', self.container_name, '--mount', mount_str, '-d', self.build_tag, 'tail', '-f',
+        self.run_command('podman run', [
+            'podman', 'run', '--name', self.container_name, '--mount', mount_str, '-d', self.build_tag, 'tail', '-f',
             '/dev/null'
         ])
 
@@ -76,13 +76,13 @@
             ['mkdir', '-p', '/tmp/libchrome', '/tmp/modpb64'],
 
             # Run the dpkg builder for modp_b64
-            ['/root/src/system/build/dpkg/modp_b64/gen-src-pkg.sh', '/tmp/modpb64'],
+            [f'{SRC_MOUNT}/system/build/dpkg/modp_b64/gen-src-pkg.sh', '/tmp/modpb64'],
 
             # Install modp_b64 since libchrome depends on it
             ['find', '/tmp/modpb64', '-name', 'modp*.deb', '-exec', 'dpkg', '-i', '{}', '+'],
 
             # Run the dpkg builder for libchrome
-            ['/root/src/system/build/dpkg/libchrome/gen-src-pkg.sh', '/tmp/libchrome'],
+            [f'{SRC_MOUNT}/system/build/dpkg/libchrome/gen-src-pkg.sh', '/tmp/libchrome'],
 
             # Install libchrome.
             ['find', '/tmp/libchrome', '-name', 'libchrome_*.deb', '-exec', 'dpkg', '-i', '{}', '+'],
@@ -91,20 +91,21 @@
             ['rm', '-rf', '/tmp/libchrome', '/tmp/modpb64'],
         ]
 
-        # Run commands in container first to install everything.
-        for i, cmd in enumerate(commands):
-            self.run_command('docker exec #{}'.format(i), ['docker', 'exec', '-it', self.container_name] + cmd)
-
-        # Commit changes into the final tag name
-        self.run_command('docker commit', ['docker', 'commit', self.container_name, self.final_tag])
-
-        # Stop running the container and remove it
-        self.run_command('docker stop', ['docker', 'stop', '-t', '1', self.container_name])
-        self.run_command('docker rm', ['docker', 'rm', self.container_name])
-
-    def _check_docker_runnable(self):
         try:
-            subprocess.check_output(['docker', 'ps'], stderr=subprocess.STDOUT)
+            # Run commands in container first to install everything.
+            for i, cmd in enumerate(commands):
+                self.run_command('podman exec #{}'.format(i), ['podman', 'exec', '-it', self.container_name] + cmd)
+
+            # Commit changes into the final tag name
+            self.run_command('podman commit', ['podman', 'commit', self.container_name, self.final_tag])
+        finally:
+            # Stop running the container and remove it
+            self.run_command('podman stop', ['podman', 'stop', '-t', '1', self.container_name])
+            self.run_command('podman rm', ['podman', 'rm', self.container_name])
+
+    def _check_podman_runnable(self):
+        try:
+            subprocess.check_output(['podman', 'ps'], stderr=subprocess.STDOUT)
         except subprocess.CalledProcessError as err:
             if 'denied' in err.output.decode('utf-8'):
                 print('Run script as sudo')
@@ -113,24 +114,24 @@
 
             return False
 
-        # No exception means docker is ok
+        # No exception means podman is ok
         return True
 
     def build(self):
-        if not self._check_docker_runnable():
+        if not self._check_podman_runnable():
             return
 
-        # First build the docker image
-        self._docker_build()
+        # First build the podman image
+        self._podman_build()
 
-        # Then build libchrome and modp-b64 inside the docker image and install
+        # Then build libchrome and modp-b64 inside the podman image and install
         # them. Commit those changes to the final label.
         self._build_dpkg_and_commit()
 
 
 def main():
-    parser = argparse.ArgumentParser(description='Build docker image for Floss build environment.')
-    parser.add_argument('--tag', required=True, help='Tag for docker image. i.e. floss:latest')
+    parser = argparse.ArgumentParser(description='Build podman image for Floss build environment.')
+    parser.add_argument('--tag', required=True, help='Tag for podman image. i.e. floss:latest')
     args = parser.parse_args()
 
     # cwd should be set to same directory as this script (that's where Dockerfile
@@ -138,9 +139,9 @@
     workdir = os.path.dirname(os.path.abspath(sys.argv[0]))
     rootdir = os.path.abspath(os.path.join(workdir, '../..'))
 
-    # Build the docker image
-    dib = DockerImageBuilder(workdir, rootdir, args.tag)
-    dib.build()
+    # Build the podman image
+    pib = PodmanImageBuilder(workdir, rootdir, args.tag)
+    pib.build()
 
 
 if __name__ == '__main__':
diff --git a/floss/build/rustup/LICENSE-APACHE b/floss/build/rustup/LICENSE-APACHE
new file mode 100644
index 0000000..11069ed
--- /dev/null
+++ b/floss/build/rustup/LICENSE-APACHE
@@ -0,0 +1,201 @@
+                              Apache License
+                        Version 2.0, January 2004
+                     http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+   "License" shall mean the terms and conditions for use, reproduction,
+   and distribution as defined by Sections 1 through 9 of this document.
+
+   "Licensor" shall mean the copyright owner or entity authorized by
+   the copyright owner that is granting the License.
+
+   "Legal Entity" shall mean the union of the acting entity and all
+   other entities that control, are controlled by, or are under common
+   control with that entity. For the purposes of this definition,
+   "control" means (i) the power, direct or indirect, to cause the
+   direction or management of such entity, whether by contract or
+   otherwise, or (ii) ownership of fifty percent (50%) or more of the
+   outstanding shares, or (iii) beneficial ownership of such entity.
+
+   "You" (or "Your") shall mean an individual or Legal Entity
+   exercising permissions granted by this License.
+
+   "Source" form shall mean the preferred form for making modifications,
+   including but not limited to software source code, documentation
+   source, and configuration files.
+
+   "Object" form shall mean any form resulting from mechanical
+   transformation or translation of a Source form, including but
+   not limited to compiled object code, generated documentation,
+   and conversions to other media types.
+
+   "Work" shall mean the work of authorship, whether in Source or
+   Object form, made available under the License, as indicated by a
+   copyright notice that is included in or attached to the work
+   (an example is provided in the Appendix below).
+
+   "Derivative Works" shall mean any work, whether in Source or Object
+   form, that is based on (or derived from) the Work and for which the
+   editorial revisions, annotations, elaborations, or other modifications
+   represent, as a whole, an original work of authorship. For the purposes
+   of this License, Derivative Works shall not include works that remain
+   separable from, or merely link (or bind by name) to the interfaces of,
+   the Work and Derivative Works thereof.
+
+   "Contribution" shall mean any work of authorship, including
+   the original version of the Work and any modifications or additions
+   to that Work or Derivative Works thereof, that is intentionally
+   submitted to Licensor for inclusion in the Work by the copyright owner
+   or by an individual or Legal Entity authorized to submit on behalf of
+   the copyright owner. For the purposes of this definition, "submitted"
+   means any form of electronic, verbal, or written communication sent
+   to the Licensor or its representatives, including but not limited to
+   communication on electronic mailing lists, source code control systems,
+   and issue tracking systems that are managed by, or on behalf of, the
+   Licensor for the purpose of discussing and improving the Work, but
+   excluding communication that is conspicuously marked or otherwise
+   designated in writing by the copyright owner as "Not a Contribution."
+
+   "Contributor" shall mean Licensor and any individual or Legal Entity
+   on behalf of whom a Contribution has been received by Licensor and
+   subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   copyright license to reproduce, prepare Derivative Works of,
+   publicly display, publicly perform, sublicense, and distribute the
+   Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   (except as stated in this section) patent license to make, have made,
+   use, offer to sell, sell, import, and otherwise transfer the Work,
+   where such license applies only to those patent claims licensable
+   by such Contributor that are necessarily infringed by their
+   Contribution(s) alone or by combination of their Contribution(s)
+   with the Work to which such Contribution(s) was submitted. If You
+   institute patent litigation against any entity (including a
+   cross-claim or counterclaim in a lawsuit) alleging that the Work
+   or a Contribution incorporated within the Work constitutes direct
+   or contributory patent infringement, then any patent licenses
+   granted to You under this License for that Work shall terminate
+   as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+   Work or Derivative Works thereof in any medium, with or without
+   modifications, and in Source or Object form, provided that You
+   meet the following conditions:
+
+   (a) You must give any other recipients of the Work or
+       Derivative Works a copy of this License; and
+
+   (b) You must cause any modified files to carry prominent notices
+       stating that You changed the files; and
+
+   (c) You must retain, in the Source form of any Derivative Works
+       that You distribute, all copyright, patent, trademark, and
+       attribution notices from the Source form of the Work,
+       excluding those notices that do not pertain to any part of
+       the Derivative Works; and
+
+   (d) If the Work includes a "NOTICE" text file as part of its
+       distribution, then any Derivative Works that You distribute must
+       include a readable copy of the attribution notices contained
+       within such NOTICE file, excluding those notices that do not
+       pertain to any part of the Derivative Works, in at least one
+       of the following places: within a NOTICE text file distributed
+       as part of the Derivative Works; within the Source form or
+       documentation, if provided along with the Derivative Works; or,
+       within a display generated by the Derivative Works, if and
+       wherever such third-party notices normally appear. The contents
+       of the NOTICE file are for informational purposes only and
+       do not modify the License. You may add Your own attribution
+       notices within Derivative Works that You distribute, alongside
+       or as an addendum to the NOTICE text from the Work, provided
+       that such additional attribution notices cannot be construed
+       as modifying the License.
+
+   You may add Your own copyright statement to Your modifications and
+   may provide additional or different license terms and conditions
+   for use, reproduction, or distribution of Your modifications, or
+   for any such Derivative Works as a whole, provided Your use,
+   reproduction, and distribution of the Work otherwise complies with
+   the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+   any Contribution intentionally submitted for inclusion in the Work
+   by You to the Licensor shall be under the terms and conditions of
+   this License, without any additional terms or conditions.
+   Notwithstanding the above, nothing herein shall supersede or modify
+   the terms of any separate license agreement you may have executed
+   with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+   names, trademarks, service marks, or product names of the Licensor,
+   except as required for reasonable and customary use in describing the
+   origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+   agreed to in writing, Licensor provides the Work (and each
+   Contributor provides its Contributions) on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied, including, without limitation, any warranties or conditions
+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+   PARTICULAR PURPOSE. You are solely responsible for determining the
+   appropriateness of using or redistributing the Work and assume any
+   risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+   whether in tort (including negligence), contract, or otherwise,
+   unless required by applicable law (such as deliberate and grossly
+   negligent acts) or agreed to in writing, shall any Contributor be
+   liable to You for damages, including any direct, indirect, special,
+   incidental, or consequential damages of any character arising as a
+   result of this License or out of the use or inability to use the
+   Work (including but not limited to damages for loss of goodwill,
+   work stoppage, computer failure or malfunction, or any and all
+   other commercial damages or losses), even if such Contributor
+   has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+   the Work or Derivative Works thereof, You may choose to offer,
+   and charge a fee for, acceptance of support, warranty, indemnity,
+   or other liability obligations and/or rights consistent with this
+   License. However, in accepting such obligations, You may act only
+   on Your own behalf and on Your sole responsibility, not on behalf
+   of any other Contributor, and only if You agree to indemnify,
+   defend, and hold each Contributor harmless for any liability
+   incurred by, or claims asserted against, such Contributor by reason
+   of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+   To apply the Apache License to your work, attach the following
+   boilerplate notice, with the fields enclosed by brackets "[]"
+   replaced with your own identifying information. (Don't include
+   the brackets!)  The text should be enclosed in the appropriate
+   comment syntax for the file format. We also recommend that a
+   file or class name and description of purpose be included on the
+   same "printed page" as the copyright notice for easier
+   identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+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
+
+    http://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.
diff --git a/floss/build/rustup/README b/floss/build/rustup/README
new file mode 100644
index 0000000..cdcf953
--- /dev/null
+++ b/floss/build/rustup/README
@@ -0,0 +1,2 @@
+This is a locally downloaded copy of the Rustup script using:
+curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs
diff --git a/floss/build/rustup/rustup.sh b/floss/build/rustup/rustup.sh
new file mode 100755
index 0000000..a4a79a2
--- /dev/null
+++ b/floss/build/rustup/rustup.sh
@@ -0,0 +1,662 @@
+#!/bin/sh
+# shellcheck shell=dash
+
+# This is just a little script that can be downloaded from the internet to
+# install rustup. It just does platform detection, downloads the installer
+# and runs it.
+
+# It runs on Unix shells like {a,ba,da,k,z}sh. It uses the common `local`
+# extension. Note: Most shells limit `local` to 1 var per line, contra bash.
+
+if [ "$KSH_VERSION" = 'Version JM 93t+ 2010-03-05' ]; then
+    # The version of ksh93 that ships with many illumos systems does not
+    # support the "local" extension.  Print a message rather than fail in
+    # subtle ways later on:
+    echo 'rustup does not work with this ksh93 version; please try bash!' >&2
+    exit 1
+fi
+
+
+set -u
+
+# If RUSTUP_UPDATE_ROOT is unset or empty, default it.
+RUSTUP_UPDATE_ROOT="${RUSTUP_UPDATE_ROOT:-https://static.rust-lang.org/rustup}"
+
+#XXX: If you change anything here, please make the same changes in setup_mode.rs
+usage() {
+    cat 1>&2 <<EOF
+rustup-init 1.24.3 (c1c769109 2021-05-31)
+The installer for rustup
+
+USAGE:
+    rustup-init [FLAGS] [OPTIONS]
+
+FLAGS:
+    -v, --verbose           Enable verbose output
+    -q, --quiet             Disable progress output
+    -y                      Disable confirmation prompt.
+        --no-modify-path    Don't configure the PATH environment variable
+    -h, --help              Prints help information
+    -V, --version           Prints version information
+
+OPTIONS:
+        --default-host <default-host>              Choose a default host triple
+        --default-toolchain <default-toolchain>    Choose a default toolchain to install
+        --default-toolchain none                   Do not install any toolchains
+        --profile [minimal|default|complete]       Choose a profile
+    -c, --component <components>...                Component name to also install
+    -t, --target <targets>...                      Target name to also install
+EOF
+}
+
+main() {
+    downloader --check
+    need_cmd uname
+    need_cmd mktemp
+    need_cmd chmod
+    need_cmd mkdir
+    need_cmd rm
+    need_cmd rmdir
+
+    get_architecture || return 1
+    local _arch="$RETVAL"
+    assert_nz "$_arch" "arch"
+
+    local _ext=""
+    case "$_arch" in
+        *windows*)
+            _ext=".exe"
+            ;;
+    esac
+
+    local _url="${RUSTUP_UPDATE_ROOT}/dist/${_arch}/rustup-init${_ext}"
+
+    local _dir
+    _dir="$(ensure mktemp -d)"
+    local _file="${_dir}/rustup-init${_ext}"
+
+    local _ansi_escapes_are_valid=false
+    if [ -t 2 ]; then
+        if [ "${TERM+set}" = 'set' ]; then
+            case "$TERM" in
+                xterm*|rxvt*|urxvt*|linux*|vt*)
+                    _ansi_escapes_are_valid=true
+                ;;
+            esac
+        fi
+    fi
+
+    # check if we have to use /dev/tty to prompt the user
+    local need_tty=yes
+    for arg in "$@"; do
+        case "$arg" in
+            -h|--help)
+                usage
+                exit 0
+                ;;
+            -y)
+                # user wants to skip the prompt -- we don't need /dev/tty
+                need_tty=no
+                ;;
+            *)
+                ;;
+        esac
+    done
+
+    if $_ansi_escapes_are_valid; then
+        printf "\33[1minfo:\33[0m downloading installer\n" 1>&2
+    else
+        printf '%s\n' 'info: downloading installer' 1>&2
+    fi
+
+    ensure mkdir -p "$_dir"
+    ensure downloader "$_url" "$_file" "$_arch"
+    ensure chmod u+x "$_file"
+    if [ ! -x "$_file" ]; then
+        printf '%s\n' "Cannot execute $_file (likely because of mounting /tmp as noexec)." 1>&2
+        printf '%s\n' "Please copy the file to a location where you can execute binaries and run ./rustup-init${_ext}." 1>&2
+        exit 1
+    fi
+
+    if [ "$need_tty" = "yes" ]; then
+        # The installer is going to want to ask for confirmation by
+        # reading stdin.  This script was piped into `sh` though and
+        # doesn't have stdin to pass to its children. Instead we're going
+        # to explicitly connect /dev/tty to the installer's stdin.
+        if [ ! -t 1 ]; then
+            err "Unable to run interactively. Run with -y to accept defaults, --help for additional options"
+        fi
+
+        ignore "$_file" "$@" < /dev/tty
+    else
+        ignore "$_file" "$@"
+    fi
+
+    local _retval=$?
+
+    ignore rm "$_file"
+    ignore rmdir "$_dir"
+
+    return "$_retval"
+}
+
+check_proc() {
+    # Check for /proc by looking for the /proc/self/exe link
+    # This is only run on Linux
+    if ! test -L /proc/self/exe ; then
+        err "fatal: Unable to find /proc/self/exe.  Is /proc mounted?  Installation cannot proceed without /proc."
+    fi
+}
+
+get_bitness() {
+    need_cmd head
+    # Architecture detection without dependencies beyond coreutils.
+    # ELF files start out "\x7fELF", and the following byte is
+    #   0x01 for 32-bit and
+    #   0x02 for 64-bit.
+    # The printf builtin on some shells like dash only supports octal
+    # escape sequences, so we use those.
+    local _current_exe_head
+    _current_exe_head=$(head -c 5 /proc/self/exe )
+    if [ "$_current_exe_head" = "$(printf '\177ELF\001')" ]; then
+        echo 32
+    elif [ "$_current_exe_head" = "$(printf '\177ELF\002')" ]; then
+        echo 64
+    else
+        err "unknown platform bitness"
+    fi
+}
+
+is_host_amd64_elf() {
+    need_cmd head
+    need_cmd tail
+    # ELF e_machine detection without dependencies beyond coreutils.
+    # Two-byte field at offset 0x12 indicates the CPU,
+    # but we're interested in it being 0x3E to indicate amd64, or not that.
+    local _current_exe_machine
+    _current_exe_machine=$(head -c 19 /proc/self/exe | tail -c 1)
+    [ "$_current_exe_machine" = "$(printf '\076')" ]
+}
+
+get_endianness() {
+    local cputype=$1
+    local suffix_eb=$2
+    local suffix_el=$3
+
+    # detect endianness without od/hexdump, like get_bitness() does.
+    need_cmd head
+    need_cmd tail
+
+    local _current_exe_endianness
+    _current_exe_endianness="$(head -c 6 /proc/self/exe | tail -c 1)"
+    if [ "$_current_exe_endianness" = "$(printf '\001')" ]; then
+        echo "${cputype}${suffix_el}"
+    elif [ "$_current_exe_endianness" = "$(printf '\002')" ]; then
+        echo "${cputype}${suffix_eb}"
+    else
+        err "unknown platform endianness"
+    fi
+}
+
+get_architecture() {
+    local _ostype _cputype _bitness _arch _clibtype
+    _ostype="$(uname -s)"
+    _cputype="$(uname -m)"
+    _clibtype="gnu"
+
+    if [ "$_ostype" = Linux ]; then
+        if [ "$(uname -o)" = Android ]; then
+            _ostype=Android
+        fi
+        if ldd --version 2>&1 | grep -q 'musl'; then
+            _clibtype="musl"
+        fi
+    fi
+
+    if [ "$_ostype" = Darwin ] && [ "$_cputype" = i386 ]; then
+        # Darwin `uname -m` lies
+        if sysctl hw.optional.x86_64 | grep -q ': 1'; then
+            _cputype=x86_64
+        fi
+    fi
+
+    if [ "$_ostype" = SunOS ]; then
+        # Both Solaris and illumos presently announce as "SunOS" in "uname -s"
+        # so use "uname -o" to disambiguate.  We use the full path to the
+        # system uname in case the user has coreutils uname first in PATH,
+        # which has historically sometimes printed the wrong value here.
+        if [ "$(/usr/bin/uname -o)" = illumos ]; then
+            _ostype=illumos
+        fi
+
+        # illumos systems have multi-arch userlands, and "uname -m" reports the
+        # machine hardware name; e.g., "i86pc" on both 32- and 64-bit x86
+        # systems.  Check for the native (widest) instruction set on the
+        # running kernel:
+        if [ "$_cputype" = i86pc ]; then
+            _cputype="$(isainfo -n)"
+        fi
+    fi
+
+    case "$_ostype" in
+
+        Android)
+            _ostype=linux-android
+            ;;
+
+        Linux)
+            check_proc
+            _ostype=unknown-linux-$_clibtype
+            _bitness=$(get_bitness)
+            ;;
+
+        FreeBSD)
+            _ostype=unknown-freebsd
+            ;;
+
+        NetBSD)
+            _ostype=unknown-netbsd
+            ;;
+
+        DragonFly)
+            _ostype=unknown-dragonfly
+            ;;
+
+        Darwin)
+            _ostype=apple-darwin
+            ;;
+
+        illumos)
+            _ostype=unknown-illumos
+            ;;
+
+        MINGW* | MSYS* | CYGWIN*)
+            _ostype=pc-windows-gnu
+            ;;
+
+        *)
+            err "unrecognized OS type: $_ostype"
+            ;;
+
+    esac
+
+    case "$_cputype" in
+
+        i386 | i486 | i686 | i786 | x86)
+            _cputype=i686
+            ;;
+
+        xscale | arm)
+            _cputype=arm
+            if [ "$_ostype" = "linux-android" ]; then
+                _ostype=linux-androideabi
+            fi
+            ;;
+
+        armv6l)
+            _cputype=arm
+            if [ "$_ostype" = "linux-android" ]; then
+                _ostype=linux-androideabi
+            else
+                _ostype="${_ostype}eabihf"
+            fi
+            ;;
+
+        armv7l | armv8l)
+            _cputype=armv7
+            if [ "$_ostype" = "linux-android" ]; then
+                _ostype=linux-androideabi
+            else
+                _ostype="${_ostype}eabihf"
+            fi
+            ;;
+
+        aarch64 | arm64)
+            _cputype=aarch64
+            ;;
+
+        x86_64 | x86-64 | x64 | amd64)
+            _cputype=x86_64
+            ;;
+
+        mips)
+            _cputype=$(get_endianness mips '' el)
+            ;;
+
+        mips64)
+            if [ "$_bitness" -eq 64 ]; then
+                # only n64 ABI is supported for now
+                _ostype="${_ostype}abi64"
+                _cputype=$(get_endianness mips64 '' el)
+            fi
+            ;;
+
+        ppc)
+            _cputype=powerpc
+            ;;
+
+        ppc64)
+            _cputype=powerpc64
+            ;;
+
+        ppc64le)
+            _cputype=powerpc64le
+            ;;
+
+        s390x)
+            _cputype=s390x
+            ;;
+        riscv64)
+            _cputype=riscv64gc
+            ;;
+        *)
+            err "unknown CPU type: $_cputype"
+
+    esac
+
+    # Detect 64-bit linux with 32-bit userland
+    if [ "${_ostype}" = unknown-linux-gnu ] && [ "${_bitness}" -eq 32 ]; then
+        case $_cputype in
+            x86_64)
+                if [ -n "${RUSTUP_CPUTYPE:-}" ]; then
+                    _cputype="$RUSTUP_CPUTYPE"
+                else {
+                    # 32-bit executable for amd64 = x32
+                    if is_host_amd64_elf; then {
+                         echo "This host is running an x32 userland; as it stands, x32 support is poor," 1>&2
+                         echo "and there isn't a native toolchain -- you will have to install" 1>&2
+                         echo "multiarch compatibility with i686 and/or amd64, then select one" 1>&2
+                         echo "by re-running this script with the RUSTUP_CPUTYPE environment variable" 1>&2
+                         echo "set to i686 or x86_64, respectively." 1>&2
+                         echo 1>&2
+                         echo "You will be able to add an x32 target after installation by running" 1>&2
+                         echo "  rustup target add x86_64-unknown-linux-gnux32" 1>&2
+                         exit 1
+                    }; else
+                        _cputype=i686
+                    fi
+                }; fi
+                ;;
+            mips64)
+                _cputype=$(get_endianness mips '' el)
+                ;;
+            powerpc64)
+                _cputype=powerpc
+                ;;
+            aarch64)
+                _cputype=armv7
+                if [ "$_ostype" = "linux-android" ]; then
+                    _ostype=linux-androideabi
+                else
+                    _ostype="${_ostype}eabihf"
+                fi
+                ;;
+            riscv64gc)
+                err "riscv64 with 32-bit userland unsupported"
+                ;;
+        esac
+    fi
+
+    # Detect armv7 but without the CPU features Rust needs in that build,
+    # and fall back to arm.
+    # See https://github.com/rust-lang/rustup.rs/issues/587.
+    if [ "$_ostype" = "unknown-linux-gnueabihf" ] && [ "$_cputype" = armv7 ]; then
+        if ensure grep '^Features' /proc/cpuinfo | grep -q -v neon; then
+            # At least one processor does not have NEON.
+            _cputype=arm
+        fi
+    fi
+
+    _arch="${_cputype}-${_ostype}"
+
+    RETVAL="$_arch"
+}
+
+say() {
+    printf 'rustup: %s\n' "$1"
+}
+
+err() {
+    say "$1" >&2
+    exit 1
+}
+
+need_cmd() {
+    if ! check_cmd "$1"; then
+        err "need '$1' (command not found)"
+    fi
+}
+
+check_cmd() {
+    command -v "$1" > /dev/null 2>&1
+}
+
+assert_nz() {
+    if [ -z "$1" ]; then err "assert_nz $2"; fi
+}
+
+# Run a command that should never fail. If the command fails execution
+# will immediately terminate with an error showing the failing
+# command.
+ensure() {
+    if ! "$@"; then err "command failed: $*"; fi
+}
+
+# This is just for indicating that commands' results are being
+# intentionally ignored. Usually, because it's being executed
+# as part of error handling.
+ignore() {
+    "$@"
+}
+
+# This wraps curl or wget. Try curl first, if not installed,
+# use wget instead.
+downloader() {
+    local _dld
+    local _ciphersuites
+    local _err
+    local _status
+    if check_cmd curl; then
+        _dld=curl
+    elif check_cmd wget; then
+        _dld=wget
+    else
+        _dld='curl or wget' # to be used in error message of need_cmd
+    fi
+
+    if [ "$1" = --check ]; then
+        need_cmd "$_dld"
+    elif [ "$_dld" = curl ]; then
+        get_ciphersuites_for_curl
+        _ciphersuites="$RETVAL"
+        if [ -n "$_ciphersuites" ]; then
+            _err=$(curl --proto '=https' --tlsv1.2 --ciphers "$_ciphersuites" --silent --show-error --fail --location "$1" --output "$2" 2>&1)
+            _status=$?
+        else
+            echo "Warning: Not enforcing strong cipher suites for TLS, this is potentially less secure"
+            if ! check_help_for "$3" curl --proto --tlsv1.2; then
+                echo "Warning: Not enforcing TLS v1.2, this is potentially less secure"
+                _err=$(curl --silent --show-error --fail --location "$1" --output "$2" 2>&1)
+                _status=$?
+            else
+                _err=$(curl --proto '=https' --tlsv1.2 --silent --show-error --fail --location "$1" --output "$2" 2>&1)
+                _status=$?
+            fi
+        fi
+        if [ -n "$_err" ]; then
+            echo "$_err" >&2
+            if echo "$_err" | grep -q 404$; then
+                err "installer for platform '$3' not found, this may be unsupported"
+            fi
+        fi
+        return $_status
+    elif [ "$_dld" = wget ]; then
+        get_ciphersuites_for_wget
+        _ciphersuites="$RETVAL"
+        if [ -n "$_ciphersuites" ]; then
+            _err=$(wget --https-only --secure-protocol=TLSv1_2 --ciphers "$_ciphersuites" "$1" -O "$2" 2>&1)
+            _status=$?
+        else
+            echo "Warning: Not enforcing strong cipher suites for TLS, this is potentially less secure"
+            if ! check_help_for "$3" wget --https-only --secure-protocol; then
+                echo "Warning: Not enforcing TLS v1.2, this is potentially less secure"
+                _err=$(wget "$1" -O "$2" 2>&1)
+                _status=$?
+            else
+                _err=$(wget --https-only --secure-protocol=TLSv1_2 "$1" -O "$2" 2>&1)
+                _status=$?
+            fi
+        fi
+        if [ -n "$_err" ]; then
+            echo "$_err" >&2
+            if echo "$_err" | grep -q ' 404 Not Found$'; then
+                err "installer for platform '$3' not found, this may be unsupported"
+            fi
+        fi
+        return $_status
+    else
+        err "Unknown downloader"   # should not reach here
+    fi
+}
+
+check_help_for() {
+    local _arch
+    local _cmd
+    local _arg
+    _arch="$1"
+    shift
+    _cmd="$1"
+    shift
+
+    local _category
+    if "$_cmd" --help | grep -q 'For all options use the manual or "--help all".'; then
+      _category="all"
+    else
+      _category=""
+    fi
+
+    case "$_arch" in
+
+        *darwin*)
+        if check_cmd sw_vers; then
+            case $(sw_vers -productVersion) in
+                10.*)
+                    # If we're running on macOS, older than 10.13, then we always
+                    # fail to find these options to force fallback
+                    if [ "$(sw_vers -productVersion | cut -d. -f2)" -lt 13 ]; then
+                        # Older than 10.13
+                        echo "Warning: Detected macOS platform older than 10.13"
+                        return 1
+                    fi
+                    ;;
+                11.*)
+                    # We assume Big Sur will be OK for now
+                    ;;
+                *)
+                    # Unknown product version, warn and continue
+                    echo "Warning: Detected unknown macOS major version: $(sw_vers -productVersion)"
+                    echo "Warning TLS capabilities detection may fail"
+                    ;;
+            esac
+        fi
+        ;;
+
+    esac
+
+    for _arg in "$@"; do
+        if ! "$_cmd" --help $_category | grep -q -- "$_arg"; then
+            return 1
+        fi
+    done
+
+    true # not strictly needed
+}
+
+# Return cipher suite string specified by user, otherwise return strong TLS 1.2-1.3 cipher suites
+# if support by local tools is detected. Detection currently supports these curl backends: 
+# GnuTLS and OpenSSL (possibly also LibreSSL and BoringSSL). Return value can be empty.
+get_ciphersuites_for_curl() {
+    if [ -n "${RUSTUP_TLS_CIPHERSUITES-}" ]; then
+        # user specified custom cipher suites, assume they know what they're doing
+        RETVAL="$RUSTUP_TLS_CIPHERSUITES"
+        return
+    fi
+
+    local _openssl_syntax="no"
+    local _gnutls_syntax="no"
+    local _backend_supported="yes"
+    if curl -V | grep -q ' OpenSSL/'; then
+        _openssl_syntax="yes"
+    elif curl -V | grep -iq ' LibreSSL/'; then
+        _openssl_syntax="yes"
+    elif curl -V | grep -iq ' BoringSSL/'; then
+        _openssl_syntax="yes"
+    elif curl -V | grep -iq ' GnuTLS/'; then
+        _gnutls_syntax="yes"
+    else
+        _backend_supported="no"
+    fi
+
+    local _args_supported="no"
+    if [ "$_backend_supported" = "yes" ]; then
+        # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc.
+        if check_help_for "notspecified" "curl" "--tlsv1.2" "--ciphers" "--proto"; then
+            _args_supported="yes"
+        fi
+    fi
+
+    local _cs=""
+    if [ "$_args_supported" = "yes" ]; then
+        if [ "$_openssl_syntax" = "yes" ]; then
+            _cs=$(get_strong_ciphersuites_for "openssl")
+        elif [ "$_gnutls_syntax" = "yes" ]; then
+            _cs=$(get_strong_ciphersuites_for "gnutls")
+        fi
+    fi
+
+    RETVAL="$_cs"
+}
+
+# Return cipher suite string specified by user, otherwise return strong TLS 1.2-1.3 cipher suites
+# if support by local tools is detected. Detection currently supports these wget backends: 
+# GnuTLS and OpenSSL (possibly also LibreSSL and BoringSSL). Return value can be empty.
+get_ciphersuites_for_wget() {
+    if [ -n "${RUSTUP_TLS_CIPHERSUITES-}" ]; then
+        # user specified custom cipher suites, assume they know what they're doing
+        RETVAL="$RUSTUP_TLS_CIPHERSUITES"
+        return
+    fi
+
+    local _cs=""
+    if wget -V | grep -q '\-DHAVE_LIBSSL'; then
+        # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc.
+        if check_help_for "notspecified" "wget" "TLSv1_2" "--ciphers" "--https-only" "--secure-protocol"; then
+            _cs=$(get_strong_ciphersuites_for "openssl")
+        fi
+    elif wget -V | grep -q '\-DHAVE_LIBGNUTLS'; then
+        # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc.
+        if check_help_for "notspecified" "wget" "TLSv1_2" "--ciphers" "--https-only" "--secure-protocol"; then
+            _cs=$(get_strong_ciphersuites_for "gnutls")
+        fi
+    fi
+
+    RETVAL="$_cs"
+}
+
+# Return strong TLS 1.2-1.3 cipher suites in OpenSSL or GnuTLS syntax. TLS 1.2 
+# excludes non-ECDHE and non-AEAD cipher suites. DHE is excluded due to bad 
+# DH params often found on servers (see RFC 7919). Sequence matches or is
+# similar to Firefox 68 ESR with weak cipher suites disabled via about:config.  
+# $1 must be openssl or gnutls.
+get_strong_ciphersuites_for() {
+    if [ "$1" = "openssl" ]; then
+        # OpenSSL is forgiving of unknown values, no problems with TLS 1.3 values on versions that don't support it yet.
+        echo "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384"
+    elif [ "$1" = "gnutls" ]; then
+        # GnuTLS isn't forgiving of unknown values, so this may require a GnuTLS version that supports TLS 1.3 even if wget doesn't.
+        # Begin with SECURE128 (and higher) then remove/add to build cipher suites. Produces same 9 cipher suites as OpenSSL but in slightly different order.
+        echo "SECURE128:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:-VERS-DTLS-ALL:-CIPHER-ALL:-MAC-ALL:-KX-ALL:+AEAD:+ECDHE-ECDSA:+ECDHE-RSA:+AES-128-GCM:+CHACHA20-POLY1305:+AES-256-GCM"
+    fi 
+}
+
+main "$@" || exit 1
diff --git a/framework/java/android/bluetooth/BluetoothHapClient.java b/framework/java/android/bluetooth/BluetoothHapClient.java
index c05126d..5416d73 100644
--- a/framework/java/android/bluetooth/BluetoothHapClient.java
+++ b/framework/java/android/bluetooth/BluetoothHapClient.java
@@ -601,7 +601,7 @@
         if (DBG) log("unregisterCallback");
 
         synchronized (mCallbackExecutorMap) {
-            if (mCallbackExecutorMap.remove(callback) != null) {
+            if (mCallbackExecutorMap.remove(callback) == null) {
                 throw new IllegalArgumentException("This callback has not been registered");
             }
         }
diff --git a/framework/java/android/bluetooth/BluetoothLeAudio.java b/framework/java/android/bluetooth/BluetoothLeAudio.java
index a1015b4..0c819db 100644
--- a/framework/java/android/bluetooth/BluetoothLeAudio.java
+++ b/framework/java/android/bluetooth/BluetoothLeAudio.java
@@ -1002,7 +1002,7 @@
         if (DBG) log("unregisterCallback");
 
         synchronized (mCallbackExecutorMap) {
-            if (mCallbackExecutorMap.remove(callback) != null) {
+            if (mCallbackExecutorMap.remove(callback) == null) {
                 throw new IllegalArgumentException("This callback has not been registered");
             }
         }
diff --git a/framework/java/android/bluetooth/BluetoothLeBroadcast.java b/framework/java/android/bluetooth/BluetoothLeBroadcast.java
index 38a8b34..72eaf43 100644
--- a/framework/java/android/bluetooth/BluetoothLeBroadcast.java
+++ b/framework/java/android/bluetooth/BluetoothLeBroadcast.java
@@ -485,7 +485,7 @@
         if (DBG) log("unregisterCallback");
 
         synchronized (mCallbackExecutorMap) {
-            if (mCallbackExecutorMap.remove(callback) != null) {
+            if (mCallbackExecutorMap.remove(callback) == null) {
                 throw new IllegalArgumentException("This callback has not been registered");
             }
         }
diff --git a/framework/java/android/bluetooth/BluetoothProfile.java b/framework/java/android/bluetooth/BluetoothProfile.java
index 78d0e13..5c80a9b 100644
--- a/framework/java/android/bluetooth/BluetoothProfile.java
+++ b/framework/java/android/bluetooth/BluetoothProfile.java
@@ -485,8 +485,22 @@
                 return "HEARING_AID";
             case LE_AUDIO:
                 return "LE_AUDIO";
+            case VOLUME_CONTROL:
+                return "VOLUME_CONTROL";
+            case MCP_SERVER:
+                return "MCP_SERVER";
+            case CSIP_SET_COORDINATOR:
+                return "CSIP_SET_COORDINATOR";
+            case LE_AUDIO_BROADCAST:
+                return "LE_AUDIO_BROADCAST";
+            case LE_CALL_CONTROL:
+                return "LE_CALL_CONTROL";
             case HAP_CLIENT:
                 return "HAP_CLIENT";
+            case LE_AUDIO_BROADCAST_ASSISTANT:
+                return "LE_AUDIO_BROADCAST_ASSISTANT";
+            case BATTERY:
+                return "BATTERY";
             default:
                 return "UNKNOWN_PROFILE";
         }
diff --git a/system/blueberry/facade/security/facade.proto b/system/blueberry/facade/security/facade.proto
index abdcd72..b9f8a76 100644
--- a/system/blueberry/facade/security/facade.proto
+++ b/system/blueberry/facade/security/facade.proto
@@ -31,6 +31,7 @@
   rpc EnforceSecurityPolicy(SecurityPolicyMessage) returns (google.protobuf.Empty) {}
   rpc FetchEnforceSecurityPolicyEvents(google.protobuf.Empty) returns (stream EnforceSecurityPolicyMsg) {}
   rpc FetchDisconnectEvents(google.protobuf.Empty) returns (stream DisconnectMsg) {}
+  rpc FetchAdvertisingCallbackEvents(google.protobuf.Empty) returns (stream AdvertisingCallbackMsg) {}
 }
 
 message OobDataMessage {
@@ -177,3 +178,20 @@
 message DisconnectMsg {
   blueberry.facade.BluetoothAddressWithType address = 1;
 }
+
+enum AdvertisingCallbackMsgType {
+  ADVERTISING_SET_STARTED = 0;
+  OWN_ADDRESS_READ = 1;
+}
+
+enum AdvertisingSetStarted {
+  NOT_STARTED = 0;
+  STARTED = 1;
+}
+
+message AdvertisingCallbackMsg {
+  AdvertisingCallbackMsgType message_type = 1;
+  uint32 advertiser_id = 2;
+  AdvertisingSetStarted advertising_started = 3;
+  blueberry.facade.BluetoothAddress address = 4;
+}
diff --git a/system/blueberry/tests/gd/cert/event_stream.py b/system/blueberry/tests/gd/cert/event_stream.py
index dc1348a..ee3db75 100644
--- a/system/blueberry/tests/gd/cert/event_stream.py
+++ b/system/blueberry/tests/gd/cert/event_stream.py
@@ -59,7 +59,7 @@
     return '{} {}'.format(type(proto_event).__name__, text_format.MessageToString(proto_event, as_one_line=True))
 
 
-DEFAULT_TIMEOUT_SECONDS = 10
+DEFAULT_TIMEOUT_SECONDS = 30
 
 
 class EventStream(IEventStream, Closable):
diff --git a/system/blueberry/tests/gd/cert/py_le_security.py b/system/blueberry/tests/gd/cert/py_le_security.py
index ed53592..f6ef921 100644
--- a/system/blueberry/tests/gd/cert/py_le_security.py
+++ b/system/blueberry/tests/gd/cert/py_le_security.py
@@ -45,6 +45,8 @@
         self._ui_event_stream = EventStream(self._device.security.FetchUiEvents(empty_proto.Empty()))
         self._bond_event_stream = EventStream(self._device.security.FetchBondEvents(empty_proto.Empty()))
         self._helper_event_stream = EventStream(self._device.security.FetchHelperEvents(empty_proto.Empty()))
+        self._advertising_callback_event_stream = EventStream(
+            self._device.security.FetchAdvertisingCallbackEvents(empty_proto.Empty()))
 
     def get_ui_stream(self):
         return self._ui_event_stream
@@ -52,6 +54,9 @@
     def get_bond_stream(self):
         return self._bond_event_stream
 
+    def get_advertising_callback_event_stream(self):
+        return self._advertising_callback_event_stream
+
     def wait_for_ui_event_passkey(self, timeout=timedelta(seconds=3)):
         display_passkey_capture = SecurityCaptures.DisplayPasskey()
         assertThat(self._ui_event_stream).emits(display_passkey_capture, timeout=timeout)
@@ -79,3 +84,8 @@
             safeClose(self._helper_event_stream)
         else:
             logging.info("DUT: Helper Event Stream is None!")
+
+        if self._advertising_callback_event_stream is not None:
+            safeClose(self._advertising_callback_event_stream)
+        else:
+            logging.info("DUT: Advertising Callback Event Stream is None!")
diff --git a/system/blueberry/tests/gd_sl4a/security/oob_pairing_sl4a_test.py b/system/blueberry/tests/gd_sl4a/security/oob_pairing_sl4a_test.py
index bf8a70f..2e0a1e7 100644
--- a/system/blueberry/tests/gd_sl4a/security/oob_pairing_sl4a_test.py
+++ b/system/blueberry/tests/gd_sl4a/security/oob_pairing_sl4a_test.py
@@ -19,20 +19,51 @@
 
 from google.protobuf import empty_pb2 as empty_proto
 
+from bluetooth_packets_python3 import hci_packets
+
+from blueberry.tests.gd_sl4a.lib.bt_constants import ble_scan_settings_phys
 from blueberry.tests.gd_sl4a.lib.gd_sl4a_base_test import GdSl4aBaseTestClass
+from blueberry.tests.gd.cert.matchers import SecurityMatchers
+from blueberry.tests.gd.cert.py_le_security import PyLeSecurity
 from blueberry.tests.gd.cert.truth import assertThat
 
+from blueberry.facade import common_pb2 as common
+from blueberry.facade.hci import le_advertising_manager_facade_pb2 as le_advertising_facade
+from blueberry.facade.hci import le_initiator_address_facade_pb2 as le_initiator_address_facade
+from blueberry.facade.security.facade_pb2 import AdvertisingCallbackMsgType
+from blueberry.facade.security.facade_pb2 import BondMsgType
+from blueberry.facade.security.facade_pb2 import LeAuthRequirementsMessage
+from blueberry.facade.security.facade_pb2 import LeIoCapabilityMessage
+from blueberry.facade.security.facade_pb2 import LeOobDataPresentMessage
+from blueberry.facade.security.facade_pb2 import UiCallbackMsg
+from blueberry.facade.security.facade_pb2 import UiCallbackType
+from blueberry.facade.security.facade_pb2 import UiMsgType
+
+LeIoCapabilities = LeIoCapabilityMessage.LeIoCapabilities
+LeOobDataFlag = LeOobDataPresentMessage.LeOobDataFlag
+
+DISPLAY_ONLY = LeIoCapabilityMessage(capabilities=LeIoCapabilities.DISPLAY_ONLY)
+
+OOB_NOT_PRESENT = LeOobDataPresentMessage(data_present=LeOobDataFlag.NOT_PRESENT)
+
 
 class OobData:
 
-    def __init__(self):
-        pass
+    address = None
+    confirmation = None
+    randomizer = None
+
+    def __init__(self, address, confirmation, randomizer):
+        self.address = address
+        self.confirmation = confirmation
+        self.randomizer = randomizer
 
 
 class OobPairingSl4aTest(GdSl4aBaseTestClass):
     # Events sent from SL4A
     SL4A_EVENT_GENERATED = "GeneratedOobData"
     SL4A_EVENT_ERROR = "ErrorOobData"
+    SL4A_EVENT_BONDED = "Bonded"
 
     # Matches tBT_TRANSPORT
     # Used Strings because ints were causing gRPC problems
@@ -46,8 +77,10 @@
 
     def setup_test(self):
         super().setup_test()
+        self.cert_security = PyLeSecurity(self.cert)
 
     def teardown_test(self):
+        self.cert_security.close()
         super().teardown_test()
 
     def _generate_sl4a_oob_data(self, transport):
@@ -59,15 +92,79 @@
             logging.error("Failed to generate OOB data!")
             return None
         logging.info("Data received!")
-        return OobData()
+        logging.info(event_info["data"])
+        return OobData(event_info["data"]["address_with_type"], event_info["data"]["confirmation"],
+                       event_info["data"]["randomizer"])
 
     def _generate_cert_oob_data(self, transport):
         if transport == self.TRANSPORT_LE:
-            return self.cert.security.GetLeOutOfBandData(empty_proto.Empty())
+            oob_data = self.cert.security.GetLeOutOfBandData(empty_proto.Empty())
+            # GetLeOutOfBandData adds null terminator to string in C code
+            # (length 17) before passing back to python via gRPC where it is
+            # converted back to bytes. Remove the null terminator for handling
+            # in python test, since length is known to be 16 for
+            # confirmation_value and random_value
+            oob_data.confirmation_value = oob_data.confirmation_value[:-1]
+            oob_data.random_value = oob_data.random_value[:-1]
+            return oob_data
         return None
 
+    def set_cert_privacy_policy_with_random_address_but_advertise_resolvable(self, irk):
+        # Random static address below, no random resolvable address at this point
+        random_address_bytes = "DD:34:02:05:5C:EE".encode()
+        private_policy = le_initiator_address_facade.PrivacyPolicy(
+            address_policy=le_initiator_address_facade.AddressPolicy.USE_RESOLVABLE_ADDRESS,
+            address_with_type=common.BluetoothAddressWithType(
+                address=common.BluetoothAddress(address=random_address_bytes), type=common.RANDOM_DEVICE_ADDRESS),
+            rotation_irk=irk)
+        self.cert.security.SetLeInitiatorAddressPolicy(private_policy)
+        # Bluetooth MAC address must be upper case
+        return random_address_bytes.decode('utf-8').upper()
+
+    def wait_for_own_address(self):
+        own_address = common.BluetoothAddress()
+
+        def get_address(event):
+            if event.message_type == AdvertisingCallbackMsgType.OWN_ADDRESS_READ:
+                nonlocal own_address
+                own_address = event.address.address
+                return True
+            return False
+
+        assertThat(self.cert_security.get_advertising_callback_event_stream()).emits(get_address)
+        return own_address
+
+    def wait_for_advertising_set_started(self):
+        advertising_started = False
+
+        def get_advertising_set_started(event):
+            if event.message_type == AdvertisingCallbackMsgType.ADVERTISING_SET_STARTED:
+                nonlocal advertising_started
+                if event.advertising_started == 1:
+                    advertising_started = True
+                return True
+            return False
+
+        assertThat(self.cert_security.get_advertising_callback_event_stream()).emits(get_advertising_set_started)
+        return advertising_started
+
+    def wait_for_yes_no_dialog(self):
+        address_with_type = common.BluetoothAddressWithType()
+
+        def get_address_with_type(event):
+            if event.message_type == UiMsgType.DISPLAY_PAIRING_PROMPT:
+                nonlocal address_with_type
+                address_with_type = event.peer
+                return True
+            return False
+
+        assertThat(self.cert_security.get_ui_stream()).emits(get_address_with_type)
+        return address_with_type
+
     def test_sl4a_classic_generate_oob_data(self):
         oob_data = self._generate_sl4a_oob_data(self.TRANSPORT_BREDR)
+        logging.info("OOB data received")
+        logging.info(oob_data)
         assertThat(oob_data).isNotNone()
 
     def test_sl4a_classic_generate_oob_data_twice(self):
@@ -81,3 +178,65 @@
     def test_cert_ble_generate_oob_data(self):
         oob_data = self._generate_cert_oob_data(self.TRANSPORT_LE)
         assertThat(oob_data).isNotNone()
+
+    def test_sl4a_create_bond_out_of_band(self):
+        self.cert.security.SetLeIoCapability(DISPLAY_ONLY)
+        self.cert.security.SetLeOobDataPresent(OOB_NOT_PRESENT)
+        self.cert.security.SetLeAuthRequirements(LeAuthRequirementsMessage(bond=1, mitm=1, secure_connections=1))
+
+        data = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10]
+        byteArrayObject = bytearray(data)
+        irk = bytes(byteArrayObject)
+
+        DEVICE_NAME = 'Im_The_CERT!'
+        self.set_cert_privacy_policy_with_random_address_but_advertise_resolvable(irk)
+
+        gap_name = hci_packets.GapData()
+        gap_name.data_type = hci_packets.GapDataType.COMPLETE_LOCAL_NAME
+        gap_name.data = list(bytes(DEVICE_NAME, encoding='utf8'))
+        gap_data = le_advertising_facade.GapDataMsg(data=bytes(gap_name.Serialize()))
+        config = le_advertising_facade.AdvertisingConfig(
+            advertisement=[gap_data],
+            interval_min=512,
+            interval_max=768,
+            advertising_type=le_advertising_facade.AdvertisingEventType.ADV_IND,
+            own_address_type=common.USE_RANDOM_DEVICE_ADDRESS,
+            channel_map=7,
+            filter_policy=le_advertising_facade.AdvertisingFilterPolicy.ALL_DEVICES)
+        extended_config = le_advertising_facade.ExtendedAdvertisingConfig(
+            include_tx_power=True,
+            connectable=True,
+            legacy_pdus=True,
+            advertising_config=config,
+            secondary_advertising_phy=ble_scan_settings_phys["1m"])
+        request = le_advertising_facade.ExtendedCreateAdvertiserRequest(config=extended_config)
+        create_response = self.cert.hci_le_advertising_manager.ExtendedCreateAdvertiser(request)
+
+        self.wait_for_advertising_set_started()
+
+        get_own_address_request = le_advertising_facade.GetOwnAddressRequest(
+            advertiser_id=create_response.advertiser_id)
+        self.cert.hci_le_advertising_manager.GetOwnAddress(get_own_address_request)
+        advertising_address = self.wait_for_own_address()
+
+        oob_data = self._generate_cert_oob_data(self.TRANSPORT_LE)
+        assertThat(oob_data).isNotNone()
+
+        self.dut.sl4a.bluetoothCreateBondOutOfBand(
+            advertising_address.decode("utf-8").upper(), self.TRANSPORT_LE, oob_data.confirmation_value.hex(),
+            oob_data.random_value.hex())
+
+        address_with_type = self.wait_for_yes_no_dialog()
+        self.cert.security.SendUiCallback(
+            UiCallbackMsg(
+                message_type=UiCallbackType.PAIRING_PROMPT, boolean=True, unique_id=1, address=address_with_type))
+
+        assertThat(self.cert_security.get_bond_stream()).emits(SecurityMatchers.BondMsg(BondMsgType.DEVICE_BONDED))
+
+        try:
+            bond_state = self.dut.ed.pop_event(self.SL4A_EVENT_BONDED, self.default_timeout)
+        except queue.Empty as error:
+            logging.error("Failed to generate OOB data!")
+
+        assertThat(bond_state).isNotNone()
+        assertThat(bond_state["data"]["bonded_state"]).isEqualTo(True)
diff --git a/system/bta/le_audio/client.cc b/system/bta/le_audio/client.cc
index b005037..b4bf14d 100644
--- a/system/bta/le_audio/client.cc
+++ b/system/bta/le_audio/client.cc
@@ -3299,11 +3299,11 @@
         stream_setup_start_timestamp_ = 0;
         if (group && group->IsPendingConfiguration()) {
           SuspendedForReconfiguration();
-          if (!groupStateMachine_->ConfigureStream(group,
-                                                   current_context_type_)) {
-            // DO SOMETHING
+          if (groupStateMachine_->ConfigureStream(group,
+                                                  current_context_type_)) {
+            /* If configuration succeed wait for new status. */
+            return;
           }
-          return;
         }
         CancelStreamingRequest();
         HandlePendingAvailableContexts(group);
diff --git a/system/bta/le_audio/state_machine.cc b/system/bta/le_audio/state_machine.cc
index 4232bd9..b897444 100644
--- a/system/bta/le_audio/state_machine.cc
+++ b/system/bta/le_audio/state_machine.cc
@@ -709,6 +709,19 @@
     }
   }
 
+  static void RemoveDataPathByCisHandle(LeAudioDevice* leAudioDevice,
+                                        uint16_t cis_conn_hdl) {
+    auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(cis_conn_hdl);
+    IsoManager::GetInstance()->RemoveIsoDataPath(
+        cis_conn_hdl,
+        (ases_pair.sink
+             ? bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionInput
+             : 0x00) |
+            (ases_pair.source ? bluetooth::hci::iso_manager::
+                                    kRemoveIsoDataPathDirectionOutput
+                              : 0x00));
+  }
+
   void ProcessHciNotifCisDisconnected(
       LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice,
       const bluetooth::hci::iso_manager::cis_disconnected_evt* event) override {
@@ -825,23 +838,14 @@
     }
 
     LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE";
-    ases_pair = leAudioDevice->GetAsesByCisConnHdl(ase->cis_conn_hdl);
-
     if (ase->data_path_state ==
         AudioStreamDataPathState::DATA_PATH_ESTABLISHED) {
-      IsoManager::GetInstance()->RemoveIsoDataPath(
-          ase->cis_conn_hdl,
-          (ases_pair.sink
-               ? bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionOutput
-               : 0x00) |
-              (ases_pair.source ? bluetooth::hci::iso_manager::
-                                      kRemoveIsoDataPathDirectionInput
-                                : 0x00));
+      RemoveDataPathByCisHandle(leAudioDevice, ase->cis_conn_hdl);
     }
   }
 
  private:
-  static constexpr uint64_t kStateTransitionTimeoutMs = 5000;
+  static constexpr uint64_t kStateTransitionTimeoutMs = 3500;
   static constexpr char kStateTransitionTimeoutMsProp[] =
       "persist.bluetooth.leaudio.device.set.state.timeoutms";
   Callbacks* state_machine_callbacks_;
@@ -1065,17 +1069,7 @@
         AudioStreamDataPathState::DATA_PATH_ESTABLISHED);
     LOG_ASSERT(ase) << __func__
                     << " Shouldn't be called without an active ASE.";
-
-    auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(ase->cis_conn_hdl);
-
-    IsoManager::GetInstance()->RemoveIsoDataPath(
-        ase->cis_conn_hdl,
-        (ases_pair.sink
-             ? bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionOutput
-             : 0x00) |
-            (ases_pair.source
-                 ? bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionInput
-                 : 0x00));
+    RemoveDataPathByCisHandle(leAudioDevice, ase->cis_conn_hdl);
   }
 
   void AseStateMachineProcessIdle(
@@ -1899,16 +1893,7 @@
 
         if (ase->data_path_state ==
             AudioStreamDataPathState::DATA_PATH_ESTABLISHED) {
-          auto ases_pair =
-              leAudioDevice->GetAsesByCisConnHdl(ase->cis_conn_hdl);
-          IsoManager::GetInstance()->RemoveIsoDataPath(
-              ase->cis_conn_hdl,
-              (ases_pair.sink ? bluetooth::hci::iso_manager::
-                                    kRemoveIsoDataPathDirectionOutput
-                              : 0x00) |
-                  (ases_pair.source ? bluetooth::hci::iso_manager::
-                                          kRemoveIsoDataPathDirectionInput
-                                    : 0x00));
+          RemoveDataPathByCisHandle(leAudioDevice, ase->cis_conn_hdl);
         } else if (ase->data_path_state ==
                        AudioStreamDataPathState::CIS_ESTABLISHED ||
                    ase->data_path_state ==
diff --git a/system/btcore/Android.bp b/system/btcore/Android.bp
index 371af6f..d2a2b34 100644
--- a/system/btcore/Android.bp
+++ b/system/btcore/Android.bp
@@ -33,7 +33,7 @@
         "com.android.bluetooth",
     ],
     target: {
-        linux_glibc: {
+        host_linux: {
             cflags: ["-D_GNU_SOURCE"],
         },
     },
diff --git a/system/btif/src/btif_sock_thread.cc b/system/btif/src/btif_sock_thread.cc
index 6744d3d..10864b9 100644
--- a/system/btif/src/btif_sock_thread.cc
+++ b/system/btif/src/btif_sock_thread.cc
@@ -442,10 +442,11 @@
   return true;
 }
 
-static void process_data_sock(int h, struct pollfd* pfds, int count) {
-  asrt(count <= ts[h].poll_count);
+static void process_data_sock(int h, struct pollfd* pfds, int pfds_count,
+                              int event_count) {
+  asrt(event_count <= pfds_count);
   int i;
-  for (i = 1; i < ts[h].poll_count; i++) {
+  for (i = 1; i < pfds_count; i++) {
     if (pfds[i].revents) {
       int ps_i = ts[h].psi[i];
       if (ts[h].ps[ps_i].pfd.fd == -1) {
@@ -512,6 +513,7 @@
     }
     if (ret != 0) {
       int need_process_data_fd = true;
+      int pfds_count = ts[h].poll_count;
       if (pfds[0].revents)  // cmd fd always is the first one
       {
         asrt(pfds[0].fd == ts[h].cmd_fdr);
@@ -524,7 +526,7 @@
         else
           ret--;  // exclude the cmd fd
       }
-      if (need_process_data_fd) process_data_sock(h, pfds, ret);
+      if (need_process_data_fd) process_data_sock(h, pfds, pfds_count, ret);
     } else {
       LOG_INFO("no data, select ret: %d", ret);
     };
diff --git a/system/embdrv/lc3/fuzzer/liblc3_fuzzer.cpp b/system/embdrv/lc3/fuzzer/liblc3_fuzzer.cpp
index 00ff5a2..88577bd 100644
--- a/system/embdrv/lc3/fuzzer/liblc3_fuzzer.cpp
+++ b/system/embdrv/lc3/fuzzer/liblc3_fuzzer.cpp
@@ -26,14 +26,15 @@
   unsigned enc_size = lc3_encoder_size(dt_us, sr_hz);
   uint16_t output_byte_count = fdp.ConsumeIntegralInRange(20, 400);
   uint16_t num_frames = lc3_frame_samples(dt_us, sr_hz);
+  uint8_t bytes_per_frame = (pcm_format == LC3_PCM_FORMAT_S16 ? 2 : 4);
 
-  if (fdp.remaining_bytes() < num_frames * 2) {
+  if (fdp.remaining_bytes() < num_frames * bytes_per_frame) {
     return;
   }
 
-  std::vector<uint16_t> input_frames(num_frames);
-  fdp.ConsumeData(input_frames.data(),
-                  input_frames.size() * 2 /* each frame is 2 bytes */);
+  std::vector<uint32_t> input_frames(
+      num_frames / (pcm_format == LC3_PCM_FORMAT_S16 ? 2 : 1));
+  fdp.ConsumeData(input_frames.data(), num_frames * bytes_per_frame);
 
   void* lc3_encoder_mem = nullptr;
   lc3_encoder_mem = malloc(enc_size);
@@ -71,7 +72,8 @@
   lc3_decoder_t lc3_decoder =
       lc3_setup_decoder(dt_us, sr_hz, 0, lc3_decoder_mem);
 
-  std::vector<uint16_t> output(num_frames);
+  std::vector<uint32_t> output(num_frames /
+                               (pcm_format == LC3_PCM_FORMAT_S16 ? 2 : 1));
   lc3_decode(lc3_decoder, input.data(), input.size(), pcm_format,
              (int16_t*)output.data(), 1);
 
diff --git a/system/embdrv/lc3/src/lc3.c b/system/embdrv/lc3/src/lc3.c
index 6977442..2f7789f 100644
--- a/system/embdrv/lc3/src/lc3.c
+++ b/system/embdrv/lc3/src/lc3.c
@@ -453,6 +453,7 @@
     enum lc3_srate sr = decoder->sr;
     enum lc3_srate sr_pcm = decoder->sr_pcm;
     int ns = LC3_NS(dt, sr_pcm);
+    int ne = LC3_NE(dt, sr);
     int nh = LC3_NH(sr_pcm);
 
     float *xf = decoder->xs;
@@ -474,6 +475,8 @@
     } else {
         lc3_plc_synthesize(dt, sr, &decoder->plc, xg, xf);
 
+        memset(xf + ne, 0, (ns - ne) * sizeof(float));
+
         lc3_mdct_inverse(dt, sr_pcm, sr, xf, xd, xs);
     }
 
diff --git a/system/gd/Android.bp b/system/gd/Android.bp
index 449639b..b608ea6 100644
--- a/system/gd/Android.bp
+++ b/system/gd/Android.bp
@@ -783,6 +783,18 @@
             // Export Python native symbols such as PyType_Type
             // suffix: ".cpython-38android-x86_64-linux-gnu",
         },
+        linux_musl_x86: {
+            enabled: false,
+        },
+        linux_musl_x86_64: {
+            include_dirs: ["external/python/cpython3/android/linux_x86_64/pyconfig"],
+            cflags: ["-DSOABI=\"cpython-38android-x86_64-linux-gnu\""],
+            // Commenting out the Linux suffix so that cpython-38-x86_64-linux-gnu
+            // Python 3.8 can also import the untagged .so library per PEP 3149
+            // Keep this change until Android py3-cmd can run ACTS, gRPC and can
+            // Export Python native symbols such as PyType_Type
+            // suffix: ".cpython-38android-x86_64-linux-gnu",
+        },
         windows: {
             enabled: false,
         },
diff --git a/system/gd/security/facade.cc b/system/gd/security/facade.cc
index 00c6e86..1b18314 100644
--- a/system/gd/security/facade.cc
+++ b/system/gd/security/facade.cc
@@ -19,6 +19,7 @@
 #include "grpc/grpc_event_queue.h"
 #include "hci/address_with_type.h"
 #include "hci/le_address_manager.h"
+#include "hci/le_advertising_manager.h"
 #include "l2cap/classic/security_policy.h"
 #include "l2cap/le/l2cap_le_module.h"
 #include "os/handler.h"
@@ -54,11 +55,20 @@
 
 }  // namespace
 
-class SecurityModuleFacadeService : public SecurityModuleFacade::Service, public ISecurityManagerListener, public UI {
+class SecurityModuleFacadeService : public SecurityModuleFacade::Service,
+                                    public ISecurityManagerListener,
+                                    public UI,
+                                    public hci::AdvertisingCallback {
  public:
   SecurityModuleFacadeService(
-      SecurityModule* security_module, L2capLeModule* l2cap_le_module, ::bluetooth::os::Handler* security_handler)
-      : security_module_(security_module), l2cap_le_module_(l2cap_le_module), security_handler_(security_handler) {
+      SecurityModule* security_module,
+      L2capLeModule* l2cap_le_module,
+      ::bluetooth::os::Handler* security_handler,
+      hci::LeAdvertisingManager* le_advertising_manager)
+      : security_module_(security_module),
+        l2cap_le_module_(l2cap_le_module),
+        security_handler_(security_handler),
+        le_advertising_manager_(le_advertising_manager) {
     security_module_->GetSecurityManager()->RegisterCallbackListener(this, security_handler_);
     security_module_->GetSecurityManager()->SetUserInterfaceHandler(this, security_handler_);
 
@@ -224,6 +234,58 @@
       ::grpc::ServerWriter<SecurityHelperMsg>* writer) override {
     return helper_events_.RunLoop(context, writer);
   }
+
+  ::grpc::Status FetchAdvertisingCallbackEvents(
+      ::grpc::ServerContext* context,
+      const ::google::protobuf::Empty* request,
+      ::grpc::ServerWriter<AdvertisingCallbackMsg>* writer) override {
+    le_advertising_manager_->RegisterAdvertisingCallback(this);
+    return advertising_callback_events_.RunLoop(context, writer);
+  }
+
+  void OnAdvertisingSetStarted(int reg_id, uint8_t advertiser_id, int8_t tx_power, AdvertisingStatus status) {
+    AdvertisingCallbackMsg advertising_set_started;
+    advertising_set_started.set_message_type(AdvertisingCallbackMsgType::ADVERTISING_SET_STARTED);
+    advertising_set_started.set_advertising_started(AdvertisingSetStarted::STARTED);
+    advertising_set_started.set_advertiser_id(advertiser_id);
+    advertising_callback_events_.OnIncomingEvent(advertising_set_started);
+  }
+
+  void OnAdvertisingEnabled(uint8_t advertiser_id, bool enable, uint8_t status) {
+    // Not used yet
+  }
+
+  void OnAdvertisingDataSet(uint8_t advertiser_id, uint8_t status) {
+    // Not used yet
+  }
+
+  void OnScanResponseDataSet(uint8_t advertiser_id, uint8_t status) {
+    // Not used yet
+  }
+
+  void OnAdvertisingParametersUpdated(uint8_t advertiser_id, int8_t tx_power, uint8_t status) {
+    // Not used yet
+  }
+
+  void OnPeriodicAdvertisingParametersUpdated(uint8_t advertiser_id, uint8_t status) {
+    // Not used yet
+  }
+
+  void OnPeriodicAdvertisingDataSet(uint8_t advertiser_id, uint8_t status) {
+    // Not used yet
+  }
+
+  void OnPeriodicAdvertisingEnabled(uint8_t advertiser_id, bool enable, uint8_t status) {
+    // Not used yet
+  }
+
+  void OnOwnAddressRead(uint8_t advertiser_id, uint8_t address_type, Address address) {
+    AdvertisingCallbackMsg get_own_address;
+    get_own_address.set_message_type(AdvertisingCallbackMsgType::OWN_ADDRESS_READ);
+    get_own_address.mutable_address()->set_address(address.ToString());
+    advertising_callback_events_.OnIncomingEvent(get_own_address);
+  }
+
   ::grpc::Status SetIoCapability(::grpc::ServerContext* context, const IoCapabilityMessage* request,
                                  ::google::protobuf::Empty* response) override {
     security_module_->GetFacadeConfigurationApi()->SetIoCapability(
@@ -532,6 +594,7 @@
   SecurityModule* security_module_;
   L2capLeModule* l2cap_le_module_;
   ::bluetooth::os::Handler* security_handler_;
+  hci::LeAdvertisingManager* le_advertising_manager_;
   ::bluetooth::grpc::GrpcEventQueue<UiMsg> ui_events_{"UI events"};
   ::bluetooth::grpc::GrpcEventQueue<BondMsg> bond_events_{"Bond events"};
   ::bluetooth::grpc::GrpcEventQueue<SecurityHelperMsg> helper_events_{"Events that don't fit any other category"};
@@ -539,6 +602,7 @@
       "Enforce Security Policy Events"};
   ::bluetooth::grpc::GrpcEventQueue<DisconnectMsg> disconnect_events_{"Disconnect events"};
   ::bluetooth::grpc::GrpcEventQueue<OobDataBondMessage> oob_events_{"OOB Data events"};
+  ::bluetooth::grpc::GrpcEventQueue<AdvertisingCallbackMsg> advertising_callback_events_{"Advertising callback events"};
   uint32_t unique_id{1};
   std::map<uint32_t, common::OnceCallback<void(bool)>> user_yes_no_callbacks_;
   std::map<uint32_t, common::OnceCallback<void(uint32_t)>> user_passkey_callbacks_;
@@ -548,12 +612,16 @@
   ::bluetooth::grpc::GrpcFacadeModule::ListDependencies(list);
   list->add<SecurityModule>();
   list->add<L2capLeModule>();
+  list->add<hci::LeAdvertisingManager>();
 }
 
 void SecurityModuleFacadeModule::Start() {
   ::bluetooth::grpc::GrpcFacadeModule::Start();
-  service_ =
-      new SecurityModuleFacadeService(GetDependency<SecurityModule>(), GetDependency<L2capLeModule>(), GetHandler());
+  service_ = new SecurityModuleFacadeService(
+      GetDependency<SecurityModule>(),
+      GetDependency<L2capLeModule>(),
+      GetHandler(),
+      GetDependency<hci::LeAdvertisingManager>());
 }
 
 void SecurityModuleFacadeModule::Stop() {
diff --git a/system/gd/security/record/security_record_storage.cc b/system/gd/security/record/security_record_storage.cc
index 257f60a..ae7128f 100644
--- a/system/gd/security/record/security_record_storage.cc
+++ b/system/gd/security/record/security_record_storage.cc
@@ -114,10 +114,10 @@
     } else if (!record->IsClassicLinkKeyValid() && record->remote_ltk) {
       mutation.Add(device.SetDeviceType(hci::DeviceType::LE));
     } else {
-      LOG_ERROR(
-          "Cannot determine device type from security record for '%s'; dropping!",
+      mutation.Add(device.SetDeviceType(hci::DeviceType::LE));
+      LOG_WARN(
+          "Cannot determine device type from security record for '%s'; defaulting to LE",
           record->GetPseudoAddress()->ToString().c_str());
-      continue;
     }
     mutation.Commit();
     SetClassicData(mutation, record, device);
diff --git a/system/osi/Android.bp b/system/osi/Android.bp
index 47a2b29..ef6404d 100644
--- a/system/osi/Android.bp
+++ b/system/osi/Android.bp
@@ -92,7 +92,7 @@
     // should be compatible for a Linux host OS. We should figure out what to do for
     // a non-Linux host OS.
     target: {
-        linux_glibc: {
+        host_linux: {
             cflags: [
                 "-D_GNU_SOURCE",
                 "-DOS_GENERIC",
@@ -145,7 +145,7 @@
         "libc++fs",
     ],
     target: {
-        linux_glibc: {
+        host_linux: {
             cflags: ["-DOS_GENERIC"],
         },
     },
diff --git a/system/service/Android.bp b/system/service/Android.bp
index 8e76bef..2d018b2 100644
--- a/system/service/Android.bp
+++ b/system/service/Android.bp
@@ -158,7 +158,7 @@
                 "test/stub_ipc_handler_binder.cc",
             ],
         },
-        linux_glibc: {
+        host_linux: {
             srcs: btserviceLinuxSrc + [
                 // TODO(bcf): Fix this test.
                 //"test/ipc_linux_unittest.cc",
diff --git a/system/stack/btm/btm_sec.cc b/system/stack/btm/btm_sec.cc
index f06cdf3..a817900 100644
--- a/system/stack/btm/btm_sec.cc
+++ b/system/stack/btm/btm_sec.cc
@@ -4409,7 +4409,11 @@
   auto addr = new RawAddress(p_dev_rec->bd_addr);
   bt_status_t status = do_in_main_thread_delayed(
       FROM_HERE, base::Bind(&btm_sec_auth_timer_timeout, addr),
+#if BASE_VER < 931007
       base::TimeDelta::FromMilliseconds(BTM_DELAY_AUTH_MS));
+#else
+      base::Milliseconds(BTM_DELAY_AUTH_MS));
+#endif
   if (status != BT_STATUS_SUCCESS) {
     LOG(ERROR) << __func__
                << ": do_in_main_thread_delayed failed. directly calling.";
diff --git a/system/test/rootcanal/bluetooth_hci.cc b/system/test/rootcanal/bluetooth_hci.cc
index 7ceff67..0df9bd8 100644
--- a/system/test/rootcanal/bluetooth_hci.cc
+++ b/system/test/rootcanal/bluetooth_hci.cc
@@ -25,8 +25,9 @@
 
 #include "hci_internals.h"
 #include "log/log.h"
-#include "model/devices/hci_socket_device.h"
+#include "model/devices/hci_device.h"
 #include "model/devices/link_layer_socket_device.h"
+#include "model/hci/hci_socket_transport.h"
 
 namespace android {
 namespace hardware {
@@ -37,7 +38,8 @@
 using android::hardware::hidl_vec;
 using rootcanal::AsyncTaskId;
 using rootcanal::DualModeController;
-using rootcanal::HciSocketDevice;
+using rootcanal::HciDevice;
+using rootcanal::HciSocketTransport;
 using rootcanal::LinkLayerSocketDevice;
 using rootcanal::TaskCallback;
 
@@ -205,7 +207,8 @@
     SetUpTestChannel();
     SetUpHciServer([this](std::shared_ptr<AsyncDataChannel> socket,
                           AsyncDataChannelServer* srv) {
-      test_model_.AddHciConnection(HciSocketDevice::Create(socket, ""));
+      auto transport = HciSocketTransport::Create(socket);
+      test_model_.AddHciConnection(HciDevice::Create(transport, ""));
       srv->StartListening();
     });
     SetUpLinkLayerServer([this](std::shared_ptr<AsyncDataChannel> socket,
diff --git a/tools/pdl/src/main.rs b/tools/pdl/src/main.rs
index 5f488fd..7309f98 100644
--- a/tools/pdl/src/main.rs
+++ b/tools/pdl/src/main.rs
@@ -21,10 +21,12 @@
 #[derive(Debug, StructOpt)]
 #[structopt(name = "pdl-parser", about = "Packet Description Language parser tool.")]
 struct Opt {
-    #[structopt(short, long = "--version", help = "Print tool version and exit.")]
+    /// Print tool version and exit.
+    #[structopt(short, long = "--version")]
     version: bool,
 
-    #[structopt(name = "FILE", help = "Input file.")]
+    /// Input file.
+    #[structopt(name = "FILE")]
     input_file: String,
 }
 
diff --git a/tools/rootcanal/Android.bp b/tools/rootcanal/Android.bp
index d2d893a..66e846f 100644
--- a/tools/rootcanal/Android.bp
+++ b/tools/rootcanal/Android.bp
@@ -80,13 +80,14 @@
         "model/controller/security_manager.cc",
         "model/devices/device.cc",
         "model/devices/device_properties.cc",
-        "model/devices/h4_data_channel_packetizer.cc",
-        "model/devices/h4_packetizer.cc",
-        "model/devices/h4_parser.cc",
-        "model/devices/hci_protocol.cc",
-        "model/devices/hci_socket_device.cc",
+        "model/devices/hci_device.cc",
         "model/devices/link_layer_socket_device.cc",
         "model/devices/remote_loopback_device.cc",
+        "model/hci/h4_data_channel_packetizer.cc",
+        "model/hci/h4_packetizer.cc",
+        "model/hci/h4_parser.cc",
+        "model/hci/hci_protocol.cc",
+        "model/hci/hci_socket_transport.cc",
         "model/setup/async_manager.cc",
         "model/setup/phy_layer_factory.cc",
         "model/setup/test_channel_transport.cc",
@@ -230,9 +231,9 @@
         "gd_defaults",
     ],
     srcs: [
-        "model/devices/h4_packetizer.cc",
-        "model/devices/h4_parser.cc",
-        "model/devices/hci_protocol.cc",
+        "model/hci/h4_packetizer.cc",
+        "model/hci/h4_parser.cc",
+        "model/hci/hci_protocol.cc",
     ],
 
     local_include_dirs: [
diff --git a/tools/rootcanal/desktop/test_environment.cc b/tools/rootcanal/desktop/test_environment.cc
index 530abab..261dc43 100644
--- a/tools/rootcanal/desktop/test_environment.cc
+++ b/tools/rootcanal/desktop/test_environment.cc
@@ -20,8 +20,8 @@
 #include <utility>      // for move
 #include <vector>       // for vector
 
-#include "model/devices/hci_socket_device.h"         // for HciSocketDevice
 #include "model/devices/link_layer_socket_device.h"  // for LinkLayerSocketDevice
+#include "model/hci/hci_socket_transport.h"          // for HciSocketTransport
 #include "net/async_data_channel.h"                  // for AsyncDataChannel
 #include "net/async_data_channel_connector.h"  // for AsyncDataChannelConnector
 #include "os/log.h"  // for LOG_INFO, LOG_ERROR, LOG_WARN
@@ -31,7 +31,8 @@
 namespace root_canal {
 
 using rootcanal::AsyncTaskId;
-using rootcanal::HciSocketDevice;
+using rootcanal::HciDevice;
+using rootcanal::HciSocketTransport;
 using rootcanal::LinkLayerSocketDevice;
 using rootcanal::TaskCallback;
 
@@ -57,8 +58,9 @@
   SetUpTestChannel();
   SetUpHciServer([this](std::shared_ptr<AsyncDataChannel> socket,
                         AsyncDataChannelServer* srv) {
+    auto transport = HciSocketTransport::Create(socket);
     test_model_.AddHciConnection(
-        HciSocketDevice::Create(socket, controller_properties_file_));
+        HciDevice::Create(transport, controller_properties_file_));
     srv->StartListening();
   });
   SetUpLinkLayerServer();
diff --git a/tools/rootcanal/model/devices/hci_device.cc b/tools/rootcanal/model/devices/hci_device.cc
new file mode 100644
index 0000000..a6e48c3
--- /dev/null
+++ b/tools/rootcanal/model/devices/hci_device.cc
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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 "hci_device.h"
+
+#include "os/log.h"
+
+namespace rootcanal {
+
+HciDevice::HciDevice(std::shared_ptr<HciTransport> transport,
+                     const std::string& properties_filename)
+    : DualModeController(properties_filename), transport_(transport) {
+  advertising_interval_ms_ = std::chrono::milliseconds(1000);
+
+  page_scan_delay_ms_ = std::chrono::milliseconds(600);
+
+  properties_.SetPageScanRepetitionMode(0);
+  properties_.SetClassOfDevice(0x600420);
+  properties_.SetExtendedInquiryData({
+      12,  // length
+      9,   // Type: Device Name
+      'g',
+      'D',
+      'e',
+      'v',
+      'i',
+      'c',
+      'e',
+      '-',
+      'h',
+      'c',
+      'i',
+
+  });
+  properties_.SetName({
+      'g',
+      'D',
+      'e',
+      'v',
+      'i',
+      'c',
+      'e',
+      '-',
+      'H',
+      'C',
+      'I',
+  });
+
+  RegisterEventChannel([this](std::shared_ptr<std::vector<uint8_t>> packet) {
+    transport_->SendEvent(*packet);
+  });
+  RegisterAclChannel([this](std::shared_ptr<std::vector<uint8_t>> packet) {
+    transport_->SendAcl(*packet);
+  });
+  RegisterScoChannel([this](std::shared_ptr<std::vector<uint8_t>> packet) {
+    transport_->SendSco(*packet);
+  });
+  RegisterIsoChannel([this](std::shared_ptr<std::vector<uint8_t>> packet) {
+    transport_->SendIso(*packet);
+  });
+
+  transport_->RegisterCallbacks(
+      [this](const std::shared_ptr<std::vector<uint8_t>> command) {
+        HandleCommand(command);
+      },
+      [this](const std::shared_ptr<std::vector<uint8_t>> acl) {
+        HandleAcl(acl);
+      },
+      [this](const std::shared_ptr<std::vector<uint8_t>> sco) {
+        HandleSco(sco);
+      },
+      [this](const std::shared_ptr<std::vector<uint8_t>> iso) {
+        HandleIso(iso);
+      },
+      [this]() {
+        LOG_INFO("HCI transport closed");
+        Close();
+      });
+}
+
+void HciDevice::TimerTick() {
+  transport_->TimerTick();
+  DualModeController::TimerTick();
+}
+
+void HciDevice::Close() {
+  transport_->Close();
+  DualModeController::Close();
+}
+
+}  // namespace rootcanal
diff --git a/tools/rootcanal/model/devices/hci_device.h b/tools/rootcanal/model/devices/hci_device.h
new file mode 100644
index 0000000..eb79cd9
--- /dev/null
+++ b/tools/rootcanal/model/devices/hci_device.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+#pragma once
+
+#include <memory>  // for shared_ptr, make_...
+#include <string>  // for string
+
+#include "model/controller/dual_mode_controller.h"  // for DualModeController
+#include "model/hci/hci_transport.h"                // for HciTransport
+
+namespace rootcanal {
+
+class HciDevice : public DualModeController {
+ public:
+  HciDevice(std::shared_ptr<HciTransport> transport,
+            const std::string& properties_filename);
+  ~HciDevice() = default;
+
+  static std::shared_ptr<HciDevice> Create(
+      std::shared_ptr<HciTransport> transport,
+      const std::string& properties_filename) {
+    return std::make_shared<HciDevice>(transport, properties_filename);
+  }
+
+  std::string GetTypeString() const override { return "hci_device"; }
+
+  void TimerTick() override;
+
+  void Close() override;
+
+ private:
+  std::shared_ptr<HciTransport> transport_;
+};
+
+}  // namespace rootcanal
diff --git a/tools/rootcanal/model/devices/hci_socket_device.cc b/tools/rootcanal/model/devices/hci_socket_device.cc
deleted file mode 100644
index 83bf5e2..0000000
--- a/tools/rootcanal/model/devices/hci_socket_device.cc
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * 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
- *
- *      http://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 "hci_socket_device.h"
-
-#include <chrono>       // for milliseconds
-#include <type_traits>  // for remove_extent_t
-
-#include "model/devices/device_properties.h"  // for DeviceProperties
-#include "os/log.h"                           // for LOG_INFO, LOG_ALWAYS_FATAL
-
-using std::vector;
-
-namespace rootcanal {
-
-HciSocketDevice::HciSocketDevice(std::shared_ptr<AsyncDataChannel> socket,
-                                 const std::string& properties_filename)
-    : DualModeController(properties_filename), socket_(socket) {
-  advertising_interval_ms_ = std::chrono::milliseconds(1000);
-
-  page_scan_delay_ms_ = std::chrono::milliseconds(600);
-
-  properties_.SetPageScanRepetitionMode(0);
-  properties_.SetClassOfDevice(0x600420);
-  properties_.SetExtendedInquiryData({
-      16,  // length
-      9,   // Type: Device Name
-      'g',
-      'D',
-      'e',
-      'v',
-      'i',
-      'c',
-      'e',
-      '-',
-      'h',
-      'c',
-      'i',
-      '_',
-      'n',
-      'e',
-      't',
-  });
-  properties_.SetName({
-      'g',
-      'D',
-      'e',
-      'v',
-      'i',
-      'c',
-      'e',
-      '-',
-      'H',
-      'C',
-      'I',
-      '_',
-      'N',
-      'e',
-      't',
-  });
-
-  h4_ = H4DataChannelPacketizer(
-      socket,
-      [this](const std::vector<uint8_t>& raw_command) {
-        std::shared_ptr<std::vector<uint8_t>> packet_copy =
-            std::make_shared<std::vector<uint8_t>>(raw_command);
-        HandleCommand(packet_copy);
-      },
-      [](const std::vector<uint8_t>&) {
-        LOG_ALWAYS_FATAL("Unexpected Event in HciSocketDevice!");
-      },
-      [this](const std::vector<uint8_t>& raw_acl) {
-        std::shared_ptr<std::vector<uint8_t>> packet_copy =
-            std::make_shared<std::vector<uint8_t>>(raw_acl);
-        HandleAcl(packet_copy);
-      },
-      [this](const std::vector<uint8_t>& raw_sco) {
-        std::shared_ptr<std::vector<uint8_t>> packet_copy =
-            std::make_shared<std::vector<uint8_t>>(raw_sco);
-        HandleSco(packet_copy);
-      },
-      [this](const std::vector<uint8_t>& raw_iso) {
-        std::shared_ptr<std::vector<uint8_t>> packet_copy =
-            std::make_shared<std::vector<uint8_t>>(raw_iso);
-        HandleIso(packet_copy);
-      },
-      [this]() {
-        LOG_INFO("HCI socket device disconnected");
-        Close();
-      });
-
-  RegisterEventChannel([this](std::shared_ptr<std::vector<uint8_t>> packet) {
-    SendHci(PacketType::EVENT, packet);
-  });
-  RegisterAclChannel([this](std::shared_ptr<std::vector<uint8_t>> packet) {
-    SendHci(PacketType::ACL, packet);
-  });
-  RegisterScoChannel([this](std::shared_ptr<std::vector<uint8_t>> packet) {
-    SendHci(PacketType::SCO, packet);
-  });
-  RegisterIsoChannel([this](std::shared_ptr<std::vector<uint8_t>> packet) {
-    SendHci(PacketType::ISO, packet);
-  });
-}
-
-void HciSocketDevice::TimerTick() {
-  h4_.OnDataReady(socket_);
-  DualModeController::TimerTick();
-}
-
-void HciSocketDevice::SendHci(
-    PacketType packet_type,
-    const std::shared_ptr<std::vector<uint8_t>> packet) {
-  if (!socket_ || !socket_->Connected()) {
-    LOG_INFO("Closed socket. Dropping packet of type %d",
-             static_cast<int>(packet_type));
-    return;
-  }
-  uint8_t type = static_cast<uint8_t>(packet_type);
-  h4_.Send(type, packet->data(), packet->size());
-}
-
-void HciSocketDevice::Close() {
-  if (socket_) {
-    socket_->Close();
-  }
-  DualModeController::Close();
-}
-
-}  // namespace rootcanal
diff --git a/tools/rootcanal/model/devices/hci_socket_device.h b/tools/rootcanal/model/devices/hci_socket_device.h
deleted file mode 100644
index 19324e8..0000000
--- a/tools/rootcanal/model/devices/hci_socket_device.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * 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
- *
- *      http://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.
- */
-
-#pragma once
-
-#include <cstdint>     // for uint8_t
-#include <functional>  // for __base, function
-#include <memory>      // for shared_ptr, make_...
-#include <string>      // for string
-#include <vector>      // for vector
-
-#include "model/controller/dual_mode_controller.h"     // for DualModeController
-#include "model/devices/h4_data_channel_packetizer.h"  // for ClientDisconnectC...
-#include "model/devices/hci_protocol.h"                // for PacketReadCallback
-#include "net/async_data_channel.h"                    // for AsyncDataChannel
-
-namespace rootcanal {
-
-using android::net::AsyncDataChannel;
-
-class HciSocketDevice : public DualModeController {
- public:
-  HciSocketDevice(std::shared_ptr<AsyncDataChannel> socket,
-                  const std::string& properties_filename);
-  ~HciSocketDevice() = default;
-
-  static std::shared_ptr<HciSocketDevice> Create(
-      std::shared_ptr<AsyncDataChannel> socket,
-      const std::string& properties_filename) {
-    return std::make_shared<HciSocketDevice>(socket, properties_filename);
-  }
-
-  std::string GetTypeString() const override { return "hci_socket_device"; }
-
-  void TimerTick() override;
-
-  void SendHci(PacketType packet_type,
-               const std::shared_ptr<std::vector<uint8_t>> packet);
-
-  void Close() override;
-
- private:
-  std::shared_ptr<AsyncDataChannel> socket_;
-  H4DataChannelPacketizer h4_{socket_,
-                              [](const std::vector<uint8_t>&) {},
-                              [](const std::vector<uint8_t>&) {},
-                              [](const std::vector<uint8_t>&) {},
-                              [](const std::vector<uint8_t>&) {},
-                              [](const std::vector<uint8_t>&) {},
-                              [] {}};
-};
-
-}  // namespace rootcanal
diff --git a/tools/rootcanal/model/devices/h4_data_channel_packetizer.cc b/tools/rootcanal/model/hci/h4_data_channel_packetizer.cc
similarity index 90%
rename from tools/rootcanal/model/devices/h4_data_channel_packetizer.cc
rename to tools/rootcanal/model/hci/h4_data_channel_packetizer.cc
index f089b5d..99f8875 100644
--- a/tools/rootcanal/model/devices/h4_data_channel_packetizer.cc
+++ b/tools/rootcanal/model/hci/h4_data_channel_packetizer.cc
@@ -26,10 +26,10 @@
 #include <utility>      // for move
 #include <vector>       // for vector
 
-#include "model/devices/h4_parser.h"  // for H4Parser, ClientDisconnectCa...
-#include "model/devices/hci_protocol.h"  // for PacketReadCallback, AsyncDataChannel
-#include "net/async_data_channel.h"      // for AsyncDataChannel
-#include "os/log.h"                      // for LOG_ERROR, LOG_ALWAYS_FATAL
+#include "model/hci/h4_parser.h"     // for H4Parser, ClientDisconnectCa...
+#include "model/hci/hci_protocol.h"  // for PacketReadCallback, AsyncDataChannel
+#include "net/async_data_channel.h"  // for AsyncDataChannel
+#include "os/log.h"                  // for LOG_ERROR, LOG_ALWAYS_FATAL
 
 namespace rootcanal {
 
diff --git a/tools/rootcanal/model/devices/h4_data_channel_packetizer.h b/tools/rootcanal/model/hci/h4_data_channel_packetizer.h
similarity index 100%
rename from tools/rootcanal/model/devices/h4_data_channel_packetizer.h
rename to tools/rootcanal/model/hci/h4_data_channel_packetizer.h
diff --git a/tools/rootcanal/model/devices/h4_packetizer.cc b/tools/rootcanal/model/hci/h4_packetizer.cc
similarity index 100%
rename from tools/rootcanal/model/devices/h4_packetizer.cc
rename to tools/rootcanal/model/hci/h4_packetizer.cc
diff --git a/tools/rootcanal/model/devices/h4_packetizer.h b/tools/rootcanal/model/hci/h4_packetizer.h
similarity index 100%
rename from tools/rootcanal/model/devices/h4_packetizer.h
rename to tools/rootcanal/model/hci/h4_packetizer.h
diff --git a/tools/rootcanal/model/devices/h4_parser.cc b/tools/rootcanal/model/hci/h4_parser.cc
similarity index 95%
rename from tools/rootcanal/model/devices/h4_parser.cc
rename to tools/rootcanal/model/hci/h4_parser.cc
index fa9fecf..f55be5c 100644
--- a/tools/rootcanal/model/devices/h4_parser.cc
+++ b/tools/rootcanal/model/hci/h4_parser.cc
@@ -14,7 +14,7 @@
 // limitations under the License.
 //
 
-#include "model/devices/h4_parser.h"  // for H4Parser, PacketType, H4Pars...
+#include "model/hci/h4_parser.h"  // for H4Parser, PacketType, H4Pars...
 
 #include <stddef.h>  // for size_t
 
@@ -23,8 +23,8 @@
 #include <utility>     // for move
 #include <vector>      // for vector
 
-#include "model/devices/hci_protocol.h"  // for PacketReadCallback
-#include "os/log.h"                      // for LOG_ALWAYS_FATAL, LOG_INFO
+#include "model/hci/hci_protocol.h"  // for PacketReadCallback
+#include "os/log.h"                  // for LOG_ALWAYS_FATAL, LOG_INFO
 
 namespace rootcanal {
 
diff --git a/tools/rootcanal/model/devices/h4_parser.h b/tools/rootcanal/model/hci/h4_parser.h
similarity index 98%
rename from tools/rootcanal/model/devices/h4_parser.h
rename to tools/rootcanal/model/hci/h4_parser.h
index 6886924..8a8802c 100644
--- a/tools/rootcanal/model/devices/h4_parser.h
+++ b/tools/rootcanal/model/hci/h4_parser.h
@@ -23,7 +23,7 @@
 #include <ostream>     // for operator<<, ostream
 #include <vector>      // for vector
 
-#include "model/devices/hci_protocol.h"  // for PacketReadCallback
+#include "model/hci/hci_protocol.h"  // for PacketReadCallback
 
 namespace rootcanal {
 
diff --git a/tools/rootcanal/model/devices/hci_protocol.cc b/tools/rootcanal/model/hci/hci_protocol.cc
similarity index 100%
rename from tools/rootcanal/model/devices/hci_protocol.cc
rename to tools/rootcanal/model/hci/hci_protocol.cc
diff --git a/tools/rootcanal/model/devices/hci_protocol.h b/tools/rootcanal/model/hci/hci_protocol.h
similarity index 100%
rename from tools/rootcanal/model/devices/hci_protocol.h
rename to tools/rootcanal/model/hci/hci_protocol.h
diff --git a/tools/rootcanal/model/hci/hci_socket_transport.cc b/tools/rootcanal/model/hci/hci_socket_transport.cc
new file mode 100644
index 0000000..0d6f0c8
--- /dev/null
+++ b/tools/rootcanal/model/hci/hci_socket_transport.cc
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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 "hci_socket_transport.h"
+
+#include "os/log.h"  // for LOG_INFO, LOG_ALWAYS_FATAL
+
+namespace rootcanal {
+
+HciSocketTransport::HciSocketTransport(std::shared_ptr<AsyncDataChannel> socket)
+    : socket_(socket) {}
+
+void HciSocketTransport::RegisterCallbacks(PacketCallback command_callback,
+                                           PacketCallback acl_callback,
+                                           PacketCallback sco_callback,
+                                           PacketCallback iso_callback,
+                                           CloseCallback close_callback) {
+  // TODO: Avoid the copy here by using new buffer in H4DataChannel
+  h4_ = H4DataChannelPacketizer(
+      socket_,
+      [command_callback](const std::vector<uint8_t>& raw_command) {
+        std::shared_ptr<std::vector<uint8_t>> packet_copy =
+            std::make_shared<std::vector<uint8_t>>(raw_command);
+        command_callback(packet_copy);
+      },
+      [](const std::vector<uint8_t>&) {
+        LOG_ALWAYS_FATAL("Unexpected Event in HciSocketTransport!");
+      },
+      [acl_callback](const std::vector<uint8_t>& raw_acl) {
+        std::shared_ptr<std::vector<uint8_t>> packet_copy =
+            std::make_shared<std::vector<uint8_t>>(raw_acl);
+        acl_callback(packet_copy);
+      },
+      [sco_callback](const std::vector<uint8_t>& raw_sco) {
+        std::shared_ptr<std::vector<uint8_t>> packet_copy =
+            std::make_shared<std::vector<uint8_t>>(raw_sco);
+        sco_callback(packet_copy);
+      },
+      [iso_callback](const std::vector<uint8_t>& raw_iso) {
+        std::shared_ptr<std::vector<uint8_t>> packet_copy =
+            std::make_shared<std::vector<uint8_t>>(raw_iso);
+        iso_callback(packet_copy);
+      },
+      close_callback);
+}
+
+void HciSocketTransport::TimerTick() { h4_.OnDataReady(socket_); }
+
+void HciSocketTransport::SendHci(PacketType packet_type,
+                                 const std::vector<uint8_t>& packet) {
+  if (!socket_ || !socket_->Connected()) {
+    LOG_INFO("Closed socket. Dropping packet of type %d",
+             static_cast<int>(packet_type));
+    return;
+  }
+  uint8_t type = static_cast<uint8_t>(packet_type);
+  h4_.Send(type, packet.data(), packet.size());
+}
+
+void HciSocketTransport::SendEvent(const std::vector<uint8_t>& packet) {
+  SendHci(PacketType::EVENT, packet);
+}
+
+void HciSocketTransport::SendAcl(const std::vector<uint8_t>& packet) {
+  SendHci(PacketType::ACL, packet);
+}
+
+void HciSocketTransport::SendSco(const std::vector<uint8_t>& packet) {
+  SendHci(PacketType::SCO, packet);
+}
+
+void HciSocketTransport::SendIso(const std::vector<uint8_t>& packet) {
+  SendHci(PacketType::ISO, packet);
+}
+
+void HciSocketTransport::Close() { socket_->Close(); }
+
+}  // namespace rootcanal
diff --git a/tools/rootcanal/model/hci/hci_socket_transport.h b/tools/rootcanal/model/hci/hci_socket_transport.h
new file mode 100644
index 0000000..f3f42f4
--- /dev/null
+++ b/tools/rootcanal/model/hci/hci_socket_transport.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+#pragma once
+
+#include <memory>  // for shared_ptr, make_...
+
+#include "model/hci/h4_data_channel_packetizer.h"  // for H4DataChannelP...
+#include "model/hci/hci_transport.h"               // for HciTransport
+#include "net/async_data_channel.h"                // for AsyncDataChannel
+
+namespace rootcanal {
+
+using android::net::AsyncDataChannel;
+
+class HciSocketTransport : public HciTransport {
+ public:
+  HciSocketTransport(std::shared_ptr<AsyncDataChannel> socket);
+  ~HciSocketTransport() = default;
+
+  static std::shared_ptr<HciSocketTransport> Create(
+      std::shared_ptr<AsyncDataChannel> socket) {
+    return std::make_shared<HciSocketTransport>(socket);
+  }
+
+  void SendEvent(const std::vector<uint8_t>& packet) override;
+
+  void SendAcl(const std::vector<uint8_t>& packet) override;
+
+  void SendSco(const std::vector<uint8_t>& packet) override;
+
+  void SendIso(const std::vector<uint8_t>& packet) override;
+
+  void RegisterCallbacks(PacketCallback command_callback,
+                         PacketCallback acl_callback,
+                         PacketCallback sco_callback,
+                         PacketCallback iso_callback,
+                         CloseCallback close_callback) override;
+
+  void TimerTick() override;
+
+  void Close() override;
+
+ private:
+  void SendHci(PacketType packet_type, const std::vector<uint8_t>& packet);
+
+  std::shared_ptr<AsyncDataChannel> socket_;
+  H4DataChannelPacketizer h4_{socket_,
+                              [](const std::vector<uint8_t>&) {},
+                              [](const std::vector<uint8_t>&) {},
+                              [](const std::vector<uint8_t>&) {},
+                              [](const std::vector<uint8_t>&) {},
+                              [](const std::vector<uint8_t>&) {},
+                              [] {}};
+};
+
+}  // namespace rootcanal
diff --git a/tools/rootcanal/model/hci/hci_transport.h b/tools/rootcanal/model/hci/hci_transport.h
new file mode 100644
index 0000000..8ffb349
--- /dev/null
+++ b/tools/rootcanal/model/hci/hci_transport.h
@@ -0,0 +1,51 @@
+//
+// Copyright 2022 The Android Open Source Project
+//
+// 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
+//
+// http://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.
+//
+
+#pragma once
+
+#include <functional>
+#include <vector>
+
+namespace rootcanal {
+
+using PacketCallback =
+    std::function<void(const std::shared_ptr<std::vector<uint8_t>>)>;
+using CloseCallback = std::function<void()>;
+
+class HciTransport {
+ public:
+  virtual ~HciTransport() = default;
+
+  virtual void SendEvent(const std::vector<uint8_t>& packet) = 0;
+
+  virtual void SendAcl(const std::vector<uint8_t>& packet) = 0;
+
+  virtual void SendSco(const std::vector<uint8_t>& packet) = 0;
+
+  virtual void SendIso(const std::vector<uint8_t>& packet) = 0;
+
+  virtual void RegisterCallbacks(PacketCallback command_callback,
+                                 PacketCallback acl_callback,
+                                 PacketCallback sco_callback,
+                                 PacketCallback iso_callback,
+                                 CloseCallback close_callback) = 0;
+
+  virtual void TimerTick() = 0;
+
+  virtual void Close() = 0;
+};
+
+}  // namespace rootcanal
diff --git a/tools/rootcanal/model/setup/test_model.cc b/tools/rootcanal/model/setup/test_model.cc
index 3b993b4..c2ad281 100644
--- a/tools/rootcanal/model/setup/test_model.cc
+++ b/tools/rootcanal/model/setup/test_model.cc
@@ -171,7 +171,7 @@
   AddLinkLayerConnection(dev, phy_type);
 }
 
-void TestModel::AddHciConnection(std::shared_ptr<HciSocketDevice> dev) {
+void TestModel::AddHciConnection(std::shared_ptr<HciDevice> dev) {
   size_t index = Add(std::static_pointer_cast<Device>(dev));
   std::string addr = "da:4c:10:de:17:";  // Da HCI dev
   std::stringstream stream;
diff --git a/tools/rootcanal/model/setup/test_model.h b/tools/rootcanal/model/setup/test_model.h
index 99138a8..263346c 100644
--- a/tools/rootcanal/model/setup/test_model.h
+++ b/tools/rootcanal/model/setup/test_model.h
@@ -25,7 +25,7 @@
 #include <vector>      // for vector
 
 #include "hci/address.h"                       // for Address
-#include "model/devices/hci_socket_device.h"   // for HciSocketDevice
+#include "model/devices/hci_device.h"          // for HciDevice
 #include "model/setup/async_manager.h"         // for AsyncUserId, AsyncTaskId
 #include "phy.h"                               // for Phy, Phy::Type
 #include "phy_layer_factory.h"                 // for PhyLayerFactory
@@ -76,7 +76,7 @@
 
   // Handle incoming remote connections
   void AddLinkLayerConnection(std::shared_ptr<Device> dev, Phy::Type phy_type);
-  void AddHciConnection(std::shared_ptr<HciSocketDevice> dev);
+  void AddHciConnection(std::shared_ptr<HciDevice> dev);
 
   // Handle closed remote connections (both hci & link layer)
   void OnConnectionClosed(size_t index, AsyncUserId user_id);
diff --git a/tools/rootcanal/test/h4_parser_unittest.cc b/tools/rootcanal/test/h4_parser_unittest.cc
index 0209117..9b74c1c 100644
--- a/tools/rootcanal/test/h4_parser_unittest.cc
+++ b/tools/rootcanal/test/h4_parser_unittest.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "model/devices/h4_parser.h"
+#include "model/hci/h4_parser.h"
 
 #include <gtest/gtest.h>