Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 70da5c11 authored by Dennis Cheng's avatar Dennis Cheng
Browse files

test_vendor_lib: Add test channel for run-time input

This change adds a test channel for receiving user-specified commands
and data at run-time. Specifically, the channel is intended to be used
for additional debugging capabilities and for fine-tuned control over
the test controller. Implementation-wise, the test channel is simply
another socket that the vendor manager watches on. The handling of test
channel input goes through the pipeline as normal HCI data after it is
received by the HciTransport.

A script for building and running the test vendor library and
(optionally) the test channel has been added in scripts/.

Bug: 21586676
Change-Id: I55bdeedbcd81effbe009aa62a19031637374a1e6
parent c9f59fd5
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -88,7 +88,6 @@ class HciTransport : public base::MessageLoopForIO::Watcher {
  // transport object, although |hci_fd_| can be closed by the HCI in
  // TestVendorOp().
  std::unique_ptr<base::ScopedFD> hci_fd_;

  std::unique_ptr<base::ScopedFD> vendor_fd_;

  DISALLOW_COPY_AND_ASSIGN(HciTransport);
+17 −23
Original line number Diff line number Diff line
@@ -30,43 +30,37 @@ namespace test_vendor_lib {
// and data from the HCI and to send controller events back to the host.
class PacketStream {
 public:
  // Constructs an invalid PacketStream object whose file descriptor must be set
  // before use.
  PacketStream();
  PacketStream() = default;

  virtual ~PacketStream() = default;

  // Reads a command packet and returns the packet back to the caller, along
  // with the responsibility of managing the packet.
  std::unique_ptr<CommandPacket> ReceiveCommand() const;
  // Reads a command packet from the file descriptor at |fd| and returns the
  // packet back to the caller, along with the responsibility of managing the
  // packet.
  std::unique_ptr<CommandPacket> ReceiveCommand(int fd) const;

  // Reads and interprets a single octet as a packet type octet. Validates the
  // type octet for correctness.
  serial_data_type_t ReceivePacketType() const;
  // Reads a single octet from |fd| and interprets it as a packet type octet.
  // Validates the type octet for correctness.
  serial_data_type_t ReceivePacketType(int fd) const;

  // Sends an event to the HCI. The ownership of the event is left with the
  // caller.
  bool SendEvent(const EventPacket& event) const;

  void SetFd(int fd);
  // Sends an event to file descriptor |fd|. The ownership of the event is left
  // with the caller.
  bool SendEvent(const EventPacket& event, int fd) const;

 private:
  // Checks if |type| is in the valid range from DATA_TYPE_COMMAND to
  // DATA_TYPE_SCO.
  bool ValidateTypeOctet(serial_data_type_t type) const;

  // Attempts to receive |num_octets_to_receive| into |destination|, returning
  // false if an error occurs.
  // Attempts to receive |num_octets_to_receive| into |destination| from |fd|,
  // returning false if an error occurs.
  bool ReceiveAll(std::vector<std::uint8_t>& destination,
                  size_t num_octets_to_receive) const;
                  size_t num_octets_to_receive, int fd) const;

  // Attempts to send |num_octets_to_send| from |source|, returning false if an
  // error occurs.
  // Attempts to send |num_octets_to_send| from |source| to |fd|, returning
  // false if an error occurs.
  bool SendAll(const std::vector<std::uint8_t>& source,
               size_t num_octets_to_send) const;

  // PacketStream does not take ownership of the file descriptor it uses.
  int fd_;
               size_t num_octets_to_send, int fd) const;
};

}  // namespace test_vendor_lib
+25 −3
Original line number Diff line number Diff line
@@ -62,6 +62,25 @@ class VendorManager {
  bool Run();

 private:
  // Test hook object for receiving run-time parameters and commands.
  class TestChannel {
   public:
    TestChannel() = default;

    ~TestChannel();

    // Waits for a connection request from the test channel program and
    // allocates the file descriptor to receive run-time parameters. This file
    // descriptor gets stored in |fd_|.
    bool SetUp();

    int GetFd();

   private:
    // File descriptor to watch for test hook data.
    int fd_;
  };

  VendorManager();

  ~VendorManager() = default;
@@ -90,9 +109,12 @@ class VendorManager {
  // descriptor.
  base::Thread thread_;

  // Used to handle further watching of the vendor's file descriptor after
  // WatchFileDescriptor() is called.
  base::MessageLoopForIO::FileDescriptorWatcher manager_watcher_;
  // Used to handle further watching of the vendor's/test channel's file
  // descriptor after WatchFileDescriptor() is called.
  base::MessageLoopForIO::FileDescriptorWatcher hci_watcher_;
  base::MessageLoopForIO::FileDescriptorWatcher test_channel_watcher_;

  TestChannel test_channel_;

  // This should remain the last member so it'll be destroyed and invalidate
  // its weak pointers before any other members are destroyed.
+119 −0
Original line number Diff line number Diff line
#!/bin/bash

#
# Copyright 2015 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.
#

# Builds and pushes the test vendor library to a connected device and starts
# logcat with project-specific tag filters. If the --test-channel flag is set,
# logcat is started in a separate process and the test channel is run in the
# current shell.

if [[ "$#" -ne 2 && "$#" -ne 4 ]]; then
  echo "Usage:"
  echo "./build_and_run.sh [/path/to/aosp] [device_name] or"
  echo "./build_and_run.sh [/path/to/aosp] [device_name] --test-channel [port]"
  exit 1
fi

# Exit the script if any command fails.
set -e

# The home directory for AOSP.
AOSP_ABS=$1
# The name of the device to build for.
DEVICE=$2

# The location of Bluetooth within AOSP.
BT_REL=/packages/modules/Bluetooth/system
BT_ABS=${AOSP_ABS}${BT_REL}

# The location of the test vendor library.
TEST_VENDOR_LIB_REL=/vendor_libs/test_vendor_lib
TEST_VENDOR_LIB_ABS=${BT_ABS}${TEST_VENDOR_LIB_REL}

DEVICE_TARGET_REL=/out/target/product
DEVICE_TARGET_ABS=${AOSP_ABS}${DEVICE_TARGET_REL}

VENDOR_SYMBOLS_REL=/symbols/system/vendor/lib
VENDOR_SYMBOLS_ABS=${DEVICE_TARGET_ABS}/${DEVICE}/${VENDOR_SYMBOLS_REL}

# The name of the object built by the test vendor library.
TEST_VENDOR_LIB=test-vendor.so
# The name of the regular vendor object to be replaced by $TEST_VENDOR_LIB.
VENDOR_LIB=libbt-vendor.so

if [[ "$#" -eq 4 && $3 == "--test-channel" ]]; then
  TEST_CHANNEL_PORT=$4
  TEST_CHANNEL_REL=/scripts
  TEST_CHANNEL_ABS=${TEST_VENDOR_LIB_ABS}${TEST_CHANNEL_REL}

  # Start logcat in a subshell.
  x-terminal-emulator -e "scripts/build_and_run.sh ${AOSP_ABS} ${DEVICE}"

  echo "Setting up build environment."
  cd ${AOSP_ABS}
  source build/envsetup.sh

  # Forward local port to the same port on the device.
  echo "Forwarding port ${TEST_CHANNEL_PORT} to device."
  adb forward tcp:${TEST_CHANNEL_PORT} tcp:${TEST_CHANNEL_PORT}

  # Turn Bluetooth on. Requires user approval via a dialog on the device.
  echo "Enabling Bluetooth. Please see dialog on device."
  adb shell am start -a android.bluetooth.adapter.action.REQUEST_ENABLE

  # Start the test channel once Bluetooth is on and logcat has started.
  read -p "Press [ENTER] once Bluetooth is enabling AND logcat has started."

  # Start the test channel.
  python ${TEST_CHANNEL_ABS}/test_channel.py localhost ${TEST_CHANNEL_PORT}
else
  echo "Setting up build environment."
  cd ${AOSP_ABS}
  source build/envsetup.sh

  echo "Navigating to test vendor library: ${TEST_VENDOR_LIB_ABS}"
  cd ${TEST_VENDOR_LIB_ABS}

  echo "Building test vendor library."
  mm

  echo "Remounting device rootfs."
  adb shell mount -o remount,rw /
  adb remount

  # Replace the actual vendor library with the test vendor library.
  mv ${DEVICE_TARGET_ABS}/${DEVICE}/system/lib/${TEST_VENDOR_LIB} \
    ${VENDOR_SYMBOLS_ABS}/${VENDOR_LIB}

  # Push the test vendor library to the device.
  echo "Pushing the test vendor library to device: $DEVICE"
  adb push ${VENDOR_SYMBOLS_ABS}/${VENDOR_LIB} /vendor/lib

  echo "Pushing libevent."
  adb push ${DEVICE_TARGET_ABS}/${DEVICE}/system/lib/libevent.so /system/lib/

  echo "Pushing libchrome."
  adb push ${DEVICE_TARGET_ABS}/${DEVICE}/system/lib/libchrome.so /system/lib/

  # Clear logcat.
  adb logcat -c

  # Run logcat with filters.
  adb logcat bt_btif:D bt_btif_core:D bt_hci:D bt_main:D bt_vendor:D \
   bte_logmsg:D command_packet:D dual_mode_controller:D event_packet:D \
   hci_transport:D hci_handler:D packet:D packet_stream:D vendor_manager:D *:S
fi
+110 −0
Original line number Diff line number Diff line
#
# Copyright 2015 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.
#

"""Script for sending testing parameters and commands to a Bluetooth device.

This script provides a simple shell interface for sending data at run-time to a
Bluetooth device. It is intended to be used in tandem with the test vendor
library project.

Usage:
  Option A: Script
    1. Run build_and_run.sh in scripts/ with the --test-channel flag set and the
    port to use for the test channel.
  Option B: Manual
    1. Choose a port to use for the test channel. Use 'adb forward tcp:<port>
    tcp:<port>' to forward the port to the device.
    2. In a separate shell, build and push the test vendor library to the device
    using the script mentioned in option A (i.e. without the --test-channel flag
    set).
    3. Once logcat has started, turn Bluetooth on from the device.
    4. Run this program, in the shell from step 1, with address 'localhost' and
    the port, also from step 1, as arguments.
"""

#!/usr/bin/env python

import cmd
import socket
import struct
import sys

class Connection(object):
  """Simple wrapper class for a socket object.

  Attributes:
    socket: The underlying socket created for the specified address and port.
  """

  def __init__(self, address, port):
    self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    self._socket.connect((address, port))

  def close(self):
    self._socket.close()

  def send(self, data):
    self._socket.send(data)

class TestChannel(cmd.Cmd):
  """Shell for sending test channel data to controller.

  Manages the connection for the test channel to the controller and defines a
  set of commands the user can send to the controller as well. These commands
  are processed parallel to commands sent from the device stack and used to
  provide additional debugging/testing capabilities.

  Attributes:
    connection: The communication channel to send data to the controller.
  """

  def __init__(self, address, port):
    print 'Type \'help\' for more information.'
    cmd.Cmd.__init__(self)
    self._connection = Connection(address, port)

  def do_hci_reset(self, arg):
    """Sends an HCIReset command to the controller."""
    self._connection.send(struct.pack('4b', 1, 3, 0xC, 0))

  def do_quit(self, arg):
    """Exits the test channel."""
    self._connection.close()
    print 'Goodbye.'
    return True

def main(argv):
  if len(argv) != 3:
    print 'Usage: python test_channel.py [address] [port]'
    return
  try:
    address = str(argv[1])
    port = int(argv[2])
  except ValueError:
    print 'Error parsing address or port.'
  else:
    try:
      test_channel = TestChannel(address, port)
    except socket.error, e:
      print 'Error connecting to socket: %s' % e
    except:
      print 'Error creating test channel (check arguments).'
    else:
      test_channel.prompt = '$ '
      test_channel.cmdloop()

if __name__ == '__main__':
  main(sys.argv)
Loading