L2CAP: Use new event stream model

Move event handling logic to client code for flexibility. Instead of
having multiple streams and requirements for subscribing and
unsubscribing a stream, server pushes all events to the stream, and let
the client fetch all of them at the same time. Adding an event handler
in client code which allows callbacks upon receiving an event.

Rewrote the existing L2CAP cert tests and improved the stability.

Next step is to migrate this new event stream model to other modules and
facade code.

Also handle command timeout properly (L2CAP/COS/CED/BV-08-C).

Bug: 144305062
Bug: 141557006
Test: cert/run_cert.sh
Change-Id: I2bea1c7c29ce941e1c357273cca853a665c67baa
diff --git a/gd/cert/gd_cert_device.py b/gd/cert/gd_cert_device.py
index 1cdd57d..b730b50 100644
--- a/gd/cert/gd_cert_device.py
+++ b/gd/cert/gd_cert_device.py
@@ -79,5 +79,3 @@
         self.hci.disconnection_stream = EventStream(self.hci.FetchDisconnection)
         self.hci.connection_failed_stream = EventStream(self.hci.FetchConnectionFailed)
         self.hci.acl_stream = EventStream(self.hci.FetchAclData)
-        self.l2cap.packet_stream = EventStream(self.l2cap.FetchL2capData)
-        self.l2cap.connection_complete_stream = EventStream(self.l2cap.FetchConnectionComplete)
diff --git a/gd/l2cap/classic/cert/api.proto b/gd/l2cap/classic/cert/api.proto
index a470b23..8492323 100644
--- a/gd/l2cap/classic/cert/api.proto
+++ b/gd/l2cap/classic/cert/api.proto
@@ -7,15 +7,23 @@
 
 service L2capModuleCert {
   rpc SendL2capPacket(L2capPacket) returns (google.protobuf.Empty) {}
-  rpc FetchL2capData(facade.EventStreamRequest) returns (stream L2capPacket) {}
-  rpc FetchConnectionComplete(facade.EventStreamRequest) returns (stream ConnectionCompleteEvent) {}
-  rpc SetOnIncomingConnectionRequest(SetOnIncomingConnectionRequestRequest)
-      returns (SetOnIncomingConnectionRequestResponse) {}
+
+  rpc SetupLink(SetupLinkRequest) returns (SetupLinkResponse) {}
   rpc DisconnectLink(DisconnectLinkRequest) returns (google.protobuf.Empty) {}
+
   rpc SendConnectionRequest(ConnectionRequest) returns (google.protobuf.Empty) {}
+  rpc SendConnectionResponse(ConnectionResponse) returns (SendConnectionResponseResult) {}
+
   rpc SendConfigurationRequest(ConfigurationRequest) returns (SendConfigurationRequestResult) {}
+  rpc SendConfigurationResponse(ConfigurationResponse) returns (SendConfigurationResponseResult) {}
+
   rpc SendDisconnectionRequest(DisconnectionRequest) returns (google.protobuf.Empty) {}
-  rpc FetchOpenedChannels(FetchOpenedChannelsRequest) returns (FetchOpenedChannelsResponse) {}
+  rpc SendDisconnectionResponse(DisconnectionResponse) returns (SendDisconnectionResponseResult) {}
+
+  rpc SendInformationRequest(InformationRequest) returns (SendInformationRequestResult) {}
+  rpc SendInformationResponse(InformationResponse) returns (SendInformationResponseResult) {}
+
+  rpc FetchL2capLog(FetchL2capLogRequest) returns (stream FetchL2capLogResponse) {}
 }
 
 message L2capPacket {
@@ -24,41 +32,124 @@
   bytes payload = 3;
 }
 
-message ConnectionCompleteEvent {
-  facade.BluetoothAddress remote = 1;
-}
-
-message SetOnIncomingConnectionRequestRequest {
-  bool accept = 1;
-}
-
-message SetOnIncomingConnectionRequestResponse {}
-
 message DisconnectLinkRequest {
   facade.BluetoothAddress remote = 1;
 }
 
+message SetupLinkRequest {
+  facade.BluetoothAddress remote = 1;
+}
+
+message SetupLinkResponse {}
+
 message ConnectionRequest {
   facade.BluetoothAddress remote = 1;
   uint32 psm = 2;
   uint32 scid = 3;
+  uint32 signal_id = 4;
 }
 
+message ConnectionResponse {
+  facade.BluetoothAddress remote = 1;
+  uint32 dcid = 2;
+  uint32 scid = 3;
+  uint32 signal_id = 4;
+}
+
+message SendConnectionResponseResult {}
+
 message ConfigurationRequest {
-  uint32 scid = 1;
+  uint32 dcid = 1;
+  uint32 signal_id = 2;
+  repeated string configuration = 3;
 }
 
 message SendConfigurationRequestResult {}
 
+message ConfigurationResponse {
+  uint32 scid = 1;
+  uint32 signal_id = 2;
+  repeated string configuration = 3;
+}
+
+message SendConfigurationResponseResult {}
+
 message DisconnectionRequest {
   facade.BluetoothAddress remote = 1;
   uint32 dcid = 2;
   uint32 scid = 3;
+  uint32 signal_id = 4;
 }
 
-message FetchOpenedChannelsRequest {}
+message DisconnectionResponse {
+  facade.BluetoothAddress remote = 1;
+  uint32 dcid = 2;
+  uint32 scid = 3;
+  uint32 signal_id = 4;
+}
 
-message FetchOpenedChannelsResponse {
-  repeated uint32 scid = 1;
-  repeated uint32 dcid = 2;
-}
\ No newline at end of file
+message SendDisconnectionResponseResult {}
+
+enum InformationRequestType {
+  CONNECTIONLESS_MTU = 0;
+  EXTENDED_FEATURES = 1;
+  FIXED_CHANNELS = 2;
+}
+
+message InformationRequest {
+  InformationRequestType type = 1;
+  uint32 signal_id = 4;
+}
+
+message SendInformationRequestResult {}
+
+message InformationResponse {
+  InformationRequestType type = 1;
+  uint32 data = 2;
+  uint32 signal_id = 3;
+}
+
+message SendInformationResponseResult {}
+
+message FetchL2capLogRequest {}
+
+message CommandReject {
+  uint32 signal_id = 1;
+  uint32 reason = 2;
+}
+
+message EchoRequest {
+  uint32 signal_id = 1;
+  string data = 2;
+}
+message EchoResponse {
+  uint32 signal_id = 1;
+  string data = 2;
+}
+
+message LinkUp {
+  facade.BluetoothAddress remote = 1;
+}
+
+message LinkDown {
+  facade.BluetoothAddress remote = 1;
+}
+
+message FetchL2capLogResponse {
+  oneof response {
+    L2capPacket data_packet = 1;
+    CommandReject command_reject = 2;
+    ConnectionRequest connection_request = 3;
+    ConnectionResponse connection_response = 4;
+    ConfigurationRequest configuration_request = 5;
+    ConfigurationResponse configuration_response = 6;
+    DisconnectionRequest disconnection_request = 7;
+    DisconnectionResponse disconnection_response = 8;
+    EchoRequest echo_request = 9;
+    EchoResponse echo_response = 10;
+    InformationRequest information_request = 11;
+    InformationResponse information_response = 12;
+    LinkUp link_up = 20;
+    LinkDown link_down = 21;
+  }
+}
diff --git a/gd/l2cap/classic/cert/cert.cc b/gd/l2cap/classic/cert/cert.cc
index 26c41ef..2e2ac59 100644
--- a/gd/l2cap/classic/cert/cert.cc
+++ b/gd/l2cap/classic/cert/cert.cc
@@ -16,6 +16,7 @@
 
 #include "l2cap/classic/cert/cert.h"
 
+#include <condition_variable>
 #include <cstdint>
 #include <memory>
 #include <mutex>
@@ -40,7 +41,6 @@
 using ::bluetooth::facade::EventStreamRequest;
 using ::bluetooth::packet::RawBuilder;
 
-using ::bluetooth::l2cap::classic::cert::ConnectionCompleteEvent;
 using ::bluetooth::l2cap::classic::cert::L2capPacket;
 
 namespace bluetooth {
@@ -50,6 +50,8 @@
 
 using namespace facade;
 
+constexpr auto kEventTimeout = std::chrono::seconds(1);
+
 class L2capModuleCertService : public L2capModuleCert::Service {
  public:
   L2capModuleCertService(hci::AclManager* acl_manager, os::Handler* facade_handler)
@@ -58,33 +60,16 @@
     acl_manager_->RegisterCallbacks(&acl_callbacks, handler_);
   }
 
-  class ConnectionCompleteCallback
-      : public grpc::GrpcEventStreamCallback<ConnectionCompleteEvent, ConnectionCompleteEvent> {
-   public:
-    void OnWriteResponse(ConnectionCompleteEvent* response, const ConnectionCompleteEvent& event) override {
-      response->CopyFrom(event);
-    }
-
-  } connection_complete_callback_;
-  ::bluetooth::grpc::GrpcEventStream<ConnectionCompleteEvent, ConnectionCompleteEvent> connection_complete_stream_{
-      &connection_complete_callback_};
-
-  ::grpc::Status FetchConnectionComplete(::grpc::ServerContext* context,
-                                         const ::bluetooth::facade::EventStreamRequest* request,
-                                         ::grpc::ServerWriter<ConnectionCompleteEvent>* writer) override {
-    return connection_complete_stream_.HandleRequest(context, request, writer);
-  }
-
-  ::grpc::Status SetOnIncomingConnectionRequest(
-      ::grpc::ServerContext* context,
-      const ::bluetooth::l2cap::classic::cert::SetOnIncomingConnectionRequestRequest* request,
-      ::bluetooth::l2cap::classic::cert::SetOnIncomingConnectionRequestResponse* response) override {
-    accept_incoming_connection_ = request->accept();
+  ::grpc::Status SetupLink(::grpc::ServerContext* context,
+                           const ::bluetooth::l2cap::classic::cert::SetupLinkRequest* request,
+                           ::bluetooth::l2cap::classic::cert::SetupLinkResponse* response) override {
+    hci::Address address;
+    hci::Address::FromString(request->remote().address(), address);
+    LOG_INFO("%s", address.ToString().c_str());
+    acl_manager_->CreateConnection(address);
     return ::grpc::Status::OK;
   }
 
-  bool accept_incoming_connection_ = true;
-
   ::grpc::Status SendL2capPacket(::grpc::ServerContext* context, const L2capPacket* request,
                                  ::google::protobuf::Empty* response) override {
     std::unique_ptr<RawBuilder> packet = std::make_unique<RawBuilder>();
@@ -96,36 +81,42 @@
     return ::grpc::Status::OK;
   }
 
-  static constexpr Cid kFirstDynamicChannelForIncomingRequest = kFirstDynamicChannel + 0x100;
-
   ::grpc::Status SendConnectionRequest(::grpc::ServerContext* context, const cert::ConnectionRequest* request,
                                        ::google::protobuf::Empty* response) override {
-    auto scid = request->scid();
-    if (last_connection_request_scid_ != kInvalidCid) {
-      return ::grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, "Another connection request is pending");
-    }
-    if (scid >= kFirstDynamicChannelForIncomingRequest) {
-      return ::grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, "Use scid < kFirstDynamicChannelForIncomingRequest");
-    }
-    for (const auto& cid_pair : open_channels_scid_dcid_) {
-      if (cid_pair.first == scid) {
-        return ::grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, "SCID already taken");
-      }
-    }
-    auto builder = ConnectionRequestBuilder::Create(1, request->psm(), scid);
-    auto l2cap_builder = BasicFrameBuilder::Create(1, std::move(builder));
+    auto builder = ConnectionRequestBuilder::Create(request->signal_id(), request->psm(), request->scid());
+    auto l2cap_builder = BasicFrameBuilder::Create(kClassicSignallingCid, std::move(builder));
     outgoing_packet_queue_.push(std::move(l2cap_builder));
     send_packet_from_queue();
-    last_connection_request_scid_ = scid;
     return ::grpc::Status::OK;
   }
-  Cid last_connection_request_scid_ = kInvalidCid;
-  Cid next_incoming_request_cid_ = kFirstDynamicChannelForIncomingRequest;
+
+  ::grpc::Status SendConnectionResponse(
+      ::grpc::ServerContext* context, const ::bluetooth::l2cap::classic::cert::ConnectionResponse* request,
+      ::bluetooth::l2cap::classic::cert::SendConnectionResponseResult* response) override {
+    auto builder = ConnectionResponseBuilder::Create(request->signal_id(), request->dcid(), request->scid(),
+                                                     ConnectionResponseResult::SUCCESS,
+                                                     ConnectionResponseStatus::NO_FURTHER_INFORMATION_AVAILABLE);
+    auto l2cap_builder = BasicFrameBuilder::Create(kClassicSignallingCid, std::move(builder));
+    outgoing_packet_queue_.push(std::move(l2cap_builder));
+    send_packet_from_queue();
+    return ::grpc::Status::OK;
+  }
 
   ::grpc::Status SendConfigurationRequest(
       ::grpc::ServerContext* context, const ::bluetooth::l2cap::classic::cert::ConfigurationRequest* request,
       ::bluetooth::l2cap::classic::cert::SendConfigurationRequestResult* response) override {
-    auto builder = ConfigurationRequestBuilder::Create(1, request->scid(), Continuation::END, {});
+    auto builder = ConfigurationRequestBuilder::Create(request->signal_id(), request->dcid(), Continuation::END, {});
+    auto l2cap_builder = BasicFrameBuilder::Create(kClassicSignallingCid, std::move(builder));
+    outgoing_packet_queue_.push(std::move(l2cap_builder));
+    send_packet_from_queue();
+    return ::grpc::Status::OK;
+  }
+
+  ::grpc::Status SendConfigurationResponse(
+      ::grpc::ServerContext* context, const ::bluetooth::l2cap::classic::cert::ConfigurationResponse* request,
+      ::bluetooth::l2cap::classic::cert::SendConfigurationResponseResult* response) override {
+    auto builder = ConfigurationResponseBuilder::Create(request->signal_id(), request->scid(), Continuation::END,
+                                                        ConfigurationResponseResult::SUCCESS, {});
     auto l2cap_builder = BasicFrameBuilder::Create(kClassicSignallingCid, std::move(builder));
     outgoing_packet_queue_.push(std::move(l2cap_builder));
     send_packet_from_queue();
@@ -134,7 +125,7 @@
 
   ::grpc::Status SendDisconnectionRequest(::grpc::ServerContext* context, const cert::DisconnectionRequest* request,
                                           ::google::protobuf::Empty* response) override {
-    auto builder = DisconnectionRequestBuilder::Create(3, request->dcid(), request->scid());
+    auto builder = DisconnectionRequestBuilder::Create(request->signal_id(), request->dcid(), request->scid());
     auto l2cap_builder = BasicFrameBuilder::Create(kClassicSignallingCid, std::move(builder));
     outgoing_packet_queue_.push(std::move(l2cap_builder));
     send_packet_from_queue();
@@ -142,16 +133,85 @@
     return ::grpc::Status::OK;
   }
 
-  ::grpc::Status FetchOpenedChannels(
-      ::grpc::ServerContext* context, const ::bluetooth::l2cap::classic::cert::FetchOpenedChannelsRequest* request,
-      ::bluetooth::l2cap::classic::cert::FetchOpenedChannelsResponse* response) override {
-    for (const auto& cid_pair : open_channels_scid_dcid_) {
-      response->mutable_scid()->Add(cid_pair.first);
-      response->mutable_dcid()->Add(cid_pair.second);
+  ::grpc::Status SendDisconnectionResponse(
+      ::grpc::ServerContext* context, const ::bluetooth::l2cap::classic::cert::DisconnectionResponse* request,
+      ::bluetooth::l2cap::classic::cert::SendDisconnectionResponseResult* response) override {
+    auto builder = DisconnectionResponseBuilder::Create(request->signal_id(), request->dcid(), request->scid());
+    auto l2cap_builder = BasicFrameBuilder::Create(kClassicSignallingCid, std::move(builder));
+    outgoing_packet_queue_.push(std::move(l2cap_builder));
+    send_packet_from_queue();
+    return ::grpc::Status::OK;
+  }
+
+  ::grpc::Status SendInformationRequest(
+      ::grpc::ServerContext* context, const ::bluetooth::l2cap::classic::cert::InformationRequest* request,
+      ::bluetooth::l2cap::classic::cert::SendInformationRequestResult* response) override {
+    switch (request->type()) {
+      case InformationRequestType::CONNECTIONLESS_MTU: {
+        auto builder =
+            InformationRequestBuilder::Create(request->signal_id(), InformationRequestInfoType::CONNECTIONLESS_MTU);
+        auto l2cap_builder = BasicFrameBuilder::Create(kClassicSignallingCid, std::move(builder));
+        outgoing_packet_queue_.push(std::move(l2cap_builder));
+        send_packet_from_queue();
+        break;
+      }
+      case InformationRequestType::EXTENDED_FEATURES: {
+        auto builder = InformationRequestBuilder::Create(request->signal_id(),
+                                                         InformationRequestInfoType::EXTENDED_FEATURES_SUPPORTED);
+        auto l2cap_builder = BasicFrameBuilder::Create(kClassicSignallingCid, std::move(builder));
+        outgoing_packet_queue_.push(std::move(l2cap_builder));
+        send_packet_from_queue();
+        break;
+      }
+      case InformationRequestType::FIXED_CHANNELS: {
+        auto builder = InformationRequestBuilder::Create(request->signal_id(),
+                                                         InformationRequestInfoType::FIXED_CHANNELS_SUPPORTED);
+        auto l2cap_builder = BasicFrameBuilder::Create(kClassicSignallingCid, std::move(builder));
+        outgoing_packet_queue_.push(std::move(l2cap_builder));
+        send_packet_from_queue();
+        break;
+      }
+      default:
+        break;
     }
     return ::grpc::Status::OK;
   }
-  std::vector<std::pair<uint16_t, uint16_t>> open_channels_scid_dcid_;
+
+  ::grpc::Status SendInformationResponse(
+      ::grpc::ServerContext* context, const ::bluetooth::l2cap::classic::cert::InformationResponse* request,
+      ::bluetooth::l2cap::classic::cert::SendInformationResponseResult* response) override {
+    switch (request->type()) {
+      case InformationRequestType::CONNECTIONLESS_MTU: {
+        auto builder = InformationResponseConnectionlessMtuBuilder::Create(request->signal_id(),
+                                                                           InformationRequestResult::NOT_SUPPORTED, 0);
+        auto l2cap_builder = BasicFrameBuilder::Create(kClassicSignallingCid, std::move(builder));
+        outgoing_packet_queue_.push(std::move(l2cap_builder));
+        send_packet_from_queue();
+        break;
+      }
+      case InformationRequestType::EXTENDED_FEATURES: {
+        auto builder = InformationResponseExtendedFeaturesBuilder::Create(
+            request->signal_id(), InformationRequestResult::NOT_SUPPORTED, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+        auto l2cap_builder = BasicFrameBuilder::Create(kClassicSignallingCid, std::move(builder));
+        outgoing_packet_queue_.push(std::move(l2cap_builder));
+        send_packet_from_queue();
+        break;
+      }
+      case InformationRequestType::FIXED_CHANNELS: {
+        constexpr uint64_t kSignallingChannelMask = 0x02;
+        auto builder = InformationResponseFixedChannelsBuilder::Create(
+            request->signal_id(), InformationRequestResult::SUCCESS, kSignallingChannelMask);
+        auto l2cap_builder = BasicFrameBuilder::Create(kClassicSignallingCid, std::move(builder));
+        outgoing_packet_queue_.push(std::move(l2cap_builder));
+        send_packet_from_queue();
+        break;
+      }
+      default:
+        break;
+    }
+
+    return ::grpc::Status::OK;
+  }
 
   std::unique_ptr<packet::BasePacketBuilder> enqueue_packet_to_acl() {
     auto basic_frame_builder = std::move(outgoing_packet_queue_.front());
@@ -162,10 +222,28 @@
     return basic_frame_builder;
   }
 
-  ::grpc::Status FetchL2capData(::grpc::ServerContext* context, const ::bluetooth::facade::EventStreamRequest* request,
-                                ::grpc::ServerWriter<L2capPacket>* writer) override {
-    return l2cap_stream_.HandleRequest(context, request, writer);
+  ::grpc::Status FetchL2capLog(::grpc::ServerContext* context, const FetchL2capLogRequest* request,
+                               ::grpc::ServerWriter<FetchL2capLogResponse>* writer) override {
+    while (!context->IsCancelled()) {
+      if (!l2cap_log_.empty()) {
+        auto& response = l2cap_log_.front();
+        writer->Write(response);
+        l2cap_log_.pop();
+      } else {
+        std::unique_lock<std::mutex> lock(l2cap_log_mutex_);
+        // TODO(hsz): Rather than hardcode 1 second wait time, we can add another RPC to allow client to inform the
+        // server to return the RPC early
+        auto status = l2cap_log_cv_.wait_for(lock, kEventTimeout);
+        if (status == std::cv_status::timeout) {
+          break;
+        }
+      }
+    }
+    return ::grpc::Status::OK;
   }
+  std::mutex l2cap_log_mutex_;
+  std::queue<FetchL2capLogResponse> l2cap_log_;
+  std::condition_variable l2cap_log_cv_;
 
   class L2capStreamCallback : public ::bluetooth::grpc::GrpcEventStreamCallback<L2capPacket, L2capPacket> {
    public:
@@ -176,20 +254,32 @@
   } l2cap_stream_callback_;
   ::bluetooth::grpc::GrpcEventStream<L2capPacket, L2capPacket> l2cap_stream_{&l2cap_stream_callback_};
 
+  void LogEvent(const FetchL2capLogResponse& response) {
+    l2cap_log_.push(response);
+    if (l2cap_log_.size() == 1) {
+      l2cap_log_cv_.notify_one();
+    }
+  }
+
   void on_incoming_packet() {
     auto packet = acl_connection_->GetAclQueueEnd()->TryDequeue();
     BasicFrameView basic_frame_view = BasicFrameView::Create(*packet);
     ASSERT(basic_frame_view.IsValid());
     L2capPacket l2cap_packet;
-    std::string data = std::string(packet->begin(), packet->end());
+    auto payload = basic_frame_view.GetPayload();
+    std::string data = std::string(payload.begin(), payload.end());
     l2cap_packet.set_payload(data);
     l2cap_packet.set_channel(basic_frame_view.GetChannelId());
     l2cap_stream_.OnIncomingEvent(l2cap_packet);
-
     if (basic_frame_view.GetChannelId() == kClassicSignallingCid) {
       ControlView control_view = ControlView::Create(basic_frame_view.GetPayload());
       ASSERT(control_view.IsValid());
       handle_signalling_packet(control_view);
+    } else {
+      FetchL2capLogResponse response;
+      response.mutable_data_packet()->set_channel(basic_frame_view.GetChannelId());
+      response.mutable_data_packet()->set_payload(data);
+      LogEvent(response);
     }
   }
 
@@ -203,37 +293,69 @@
   void handle_signalling_packet(ControlView control_view) {
     auto code = control_view.GetCode();
     switch (code) {
+      case CommandCode::COMMAND_REJECT: {
+        CommandRejectView view = CommandRejectView::Create(control_view);
+        ASSERT(view.IsValid());
+        FetchL2capLogResponse response;
+        response.mutable_command_reject()->set_signal_id(control_view.GetIdentifier());
+        LogEvent(response);
+        break;
+      }
       case CommandCode::CONNECTION_REQUEST: {
         ConnectionRequestView view = ConnectionRequestView::Create(control_view);
         ASSERT(view.IsValid());
-        auto builder = ConnectionResponseBuilder::Create(
-            view.GetIdentifier(), next_incoming_request_cid_, view.GetSourceCid(),
-            accept_incoming_connection_ ? ConnectionResponseResult::SUCCESS : ConnectionResponseResult::INVALID_CID,
-            ConnectionResponseStatus::NO_FURTHER_INFORMATION_AVAILABLE);
-        auto l2cap_builder = BasicFrameBuilder::Create(kClassicSignallingCid, std::move(builder));
-        outgoing_packet_queue_.push(std::move(l2cap_builder));
-        send_packet_from_queue();
-        open_channels_scid_dcid_.emplace_back(next_incoming_request_cid_, view.GetSourceCid());
-        next_incoming_request_cid_++;
+        FetchL2capLogResponse response;
+        response.mutable_connection_request()->set_signal_id(control_view.GetIdentifier());
+        response.mutable_connection_request()->set_scid(view.GetSourceCid());
+        response.mutable_connection_request()->set_psm(view.GetPsm());
+        LogEvent(response);
         break;
       }
       case CommandCode::CONNECTION_RESPONSE: {
         ConnectionResponseView view = ConnectionResponseView::Create(control_view);
         ASSERT(view.IsValid());
-        open_channels_scid_dcid_.emplace_back(last_connection_request_scid_, view.GetSourceCid());
-        last_connection_request_scid_ = kInvalidCid;
+        FetchL2capLogResponse response;
+        response.mutable_connection_response()->set_signal_id(control_view.GetIdentifier());
+        response.mutable_connection_response()->set_scid(view.GetSourceCid());
+        response.mutable_connection_response()->set_dcid(view.GetDestinationCid());
+        LogEvent(response);
         break;
       }
 
       case CommandCode::CONFIGURATION_REQUEST: {
         ConfigurationRequestView view = ConfigurationRequestView::Create(control_view);
         ASSERT(view.IsValid());
-        auto builder =
-            ConfigurationResponseBuilder::Create(view.GetIdentifier(), view.GetDestinationCid(), Continuation::END,
-                                                 ConfigurationResponseResult::SUCCESS, {});
-        auto l2cap_builder = BasicFrameBuilder::Create(kClassicSignallingCid, std::move(builder));
-        outgoing_packet_queue_.push(std::move(l2cap_builder));
-        send_packet_from_queue();
+        FetchL2capLogResponse response;
+        response.mutable_configuration_request()->set_signal_id(control_view.GetIdentifier());
+        response.mutable_configuration_request()->set_dcid(view.GetDestinationCid());
+        LogEvent(response);
+        break;
+      }
+      case CommandCode::CONFIGURATION_RESPONSE: {
+        ConfigurationResponseView view = ConfigurationResponseView::Create(control_view);
+        ASSERT(view.IsValid());
+        FetchL2capLogResponse response;
+        response.mutable_configuration_response()->set_signal_id(control_view.GetIdentifier());
+        response.mutable_configuration_response()->set_scid(view.GetSourceCid());
+        LogEvent(response);
+        break;
+      }
+      case CommandCode::DISCONNECTION_RESPONSE: {
+        DisconnectionResponseView view = DisconnectionResponseView::Create(control_view);
+        ASSERT(view.IsValid());
+        FetchL2capLogResponse response;
+        response.mutable_disconnection_response()->set_signal_id(control_view.GetIdentifier());
+        response.mutable_disconnection_response()->set_dcid(view.GetDestinationCid());
+        response.mutable_disconnection_response()->set_scid(view.GetSourceCid());
+        LogEvent(response);
+        break;
+      }
+      case CommandCode::ECHO_RESPONSE: {
+        EchoResponseView view = EchoResponseView::Create(control_view);
+        ASSERT(view.IsValid());
+        FetchL2capLogResponse response;
+        response.mutable_echo_response()->set_signal_id(control_view.GetIdentifier());
+        LogEvent(response);
         break;
       }
       case CommandCode::INFORMATION_REQUEST: {
@@ -241,36 +363,49 @@
         if (!information_request_view.IsValid()) {
           return;
         }
+        FetchL2capLogResponse log_response;
+        log_response.mutable_information_request()->set_signal_id(control_view.GetIdentifier());
         auto type = information_request_view.GetInfoType();
         switch (type) {
           case InformationRequestInfoType::CONNECTIONLESS_MTU: {
-            auto response = InformationResponseConnectionlessMtuBuilder::Create(
-                information_request_view.GetIdentifier(), InformationRequestResult::NOT_SUPPORTED, 0);
-            auto l2cap_builder = BasicFrameBuilder::Create(kClassicSignallingCid, std::move(response));
-            outgoing_packet_queue_.push(std::move(l2cap_builder));
-            send_packet_from_queue();
+            log_response.mutable_information_request()->set_type(InformationRequestType::CONNECTIONLESS_MTU);
             break;
           }
           case InformationRequestInfoType::EXTENDED_FEATURES_SUPPORTED: {
-            // TODO: implement this response
-            auto response = InformationResponseExtendedFeaturesBuilder::Create(information_request_view.GetIdentifier(),
-                                                                               InformationRequestResult::NOT_SUPPORTED,
-                                                                               0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
-            auto l2cap_builder = BasicFrameBuilder::Create(kClassicSignallingCid, std::move(response));
-            outgoing_packet_queue_.push(std::move(l2cap_builder));
-            send_packet_from_queue();
+            log_response.mutable_information_request()->set_type(InformationRequestType::EXTENDED_FEATURES);
             break;
           }
           case InformationRequestInfoType::FIXED_CHANNELS_SUPPORTED: {
-            constexpr uint64_t kSignallingChannelMask = 0x02;
-            auto response = InformationResponseFixedChannelsBuilder::Create(
-                information_request_view.GetIdentifier(), InformationRequestResult::SUCCESS, kSignallingChannelMask);
-            auto l2cap_builder = BasicFrameBuilder::Create(kClassicSignallingCid, std::move(response));
-            outgoing_packet_queue_.push(std::move(l2cap_builder));
-            send_packet_from_queue();
+            log_response.mutable_information_request()->set_type(InformationRequestType::FIXED_CHANNELS);
             break;
           }
         }
+        LogEvent(log_response);
+        break;
+      }
+      case CommandCode::INFORMATION_RESPONSE: {
+        InformationResponseView information_response_view = InformationResponseView::Create(control_view);
+        if (!information_response_view.IsValid()) {
+          return;
+        }
+        FetchL2capLogResponse log_response;
+        log_response.mutable_information_response()->set_signal_id(control_view.GetIdentifier());
+        auto type = information_response_view.GetInfoType();
+        switch (type) {
+          case InformationRequestInfoType::CONNECTIONLESS_MTU: {
+            log_response.mutable_information_response()->set_type(InformationRequestType::CONNECTIONLESS_MTU);
+            break;
+          }
+          case InformationRequestInfoType::EXTENDED_FEATURES_SUPPORTED: {
+            log_response.mutable_information_response()->set_type(InformationRequestType::EXTENDED_FEATURES);
+            break;
+          }
+          case InformationRequestInfoType::FIXED_CHANNELS_SUPPORTED: {
+            log_response.mutable_information_response()->set_type(InformationRequestType::FIXED_CHANNELS);
+            break;
+          }
+        }
+        LogEvent(log_response);
         break;
       }
       default:
@@ -287,14 +422,14 @@
    public:
     AclCallbacks(L2capModuleCertService* module) : module_(module) {}
     void OnConnectSuccess(std::unique_ptr<hci::AclConnection> connection) override {
-      ConnectionCompleteEvent event;
-      event.mutable_remote()->set_address(connection->GetAddress().ToString());
-      module_->connection_complete_stream_.OnIncomingEvent(event);
       module_->acl_connection_ = std::move(connection);
       module_->acl_connection_->RegisterDisconnectCallback(common::BindOnce([](hci::ErrorCode) {}), module_->handler_);
       module_->acl_connection_->GetAclQueueEnd()->RegisterDequeue(
           module_->handler_, common::Bind(&L2capModuleCertService::on_incoming_packet, common::Unretained(module_)));
       dequeue_registered_ = true;
+      FetchL2capLogResponse response;
+      response.mutable_link_up()->mutable_remote()->set_address(module_->acl_connection_->GetAddress().ToString());
+      module_->LogEvent(response);
     }
     void OnConnectFail(hci::Address address, hci::ErrorCode reason) override {}
 
diff --git a/gd/l2cap/classic/cert/simple_l2cap_test.py b/gd/l2cap/classic/cert/simple_l2cap_test.py
index d8a52cd..d0fafb3 100644
--- a/gd/l2cap/classic/cert/simple_l2cap_test.py
+++ b/gd/l2cap/classic/cert/simple_l2cap_test.py
@@ -31,6 +31,49 @@
 
 ASYNC_OP_TIME_SECONDS = 1  # TODO: Use events to synchronize events instead
 
+class EventHandler:
+    def __init__(self):
+        self._handler_map = {}
+
+    def on(self, matcher, func):
+        self._handler_map[matcher] = func
+
+    def execute(self, grpc_stream):
+        for result in grpc_stream:
+            for matcher, func in self._handler_map.items():
+                if matcher(result):
+                    func(result)
+
+def is_connection_request(log):
+    return log.HasField("connection_request")
+
+def is_connection_response(log):
+    return log.HasField("connection_response")
+
+def is_configuration_request(log):
+    return log.HasField("configuration_request")
+
+def is_configuration_response(log):
+    return log.HasField("configuration_response")
+
+def is_disconnection_request(log):
+    return log.HasField("disconnection_request")
+
+def is_disconnection_response(log):
+    return log.HasField("disconnection_response")
+
+def is_echo_response(log):
+    return log.HasField("echo_response")
+
+def is_information_request(log):
+    return log.HasField("information_request")
+
+def is_information_response(log):
+    return log.HasField("information_response")
+
+def is_command_reject(log):
+    return log.HasField("command_reject")
+
 class SimpleL2capTest(GdBaseTestClass):
     def setup_test(self):
         self.device_under_test = self.gd_devices[0]
@@ -59,6 +102,53 @@
         self.cert_address = common_pb2.BluetoothAddress(
             address=self.cert_device.address)
 
+        log_event_handler = EventHandler()
+        self.next_scid = 0x40
+        self.scid_dcid_map = {}
+        def handle_connection_request(log):
+            log = log.connection_request
+            self.cert_device.l2cap.SendConnectionResponse(l2cap_cert_pb2.ConnectionResponse(dcid=self.next_scid,scid=log.scid,
+                                                                                            signal_id=log.signal_id))
+            self.scid_dcid_map[self.next_scid] = log.scid
+            self.next_scid += 1
+            self.cert_device.l2cap.SendConfigurationRequest(l2cap_cert_pb2.ConfigurationRequest(dcid=log.scid,
+                                                                                            signal_id=log.signal_id+1))
+        log_event_handler.on(is_connection_request, handle_connection_request)
+
+        def handle_connection_response(log):
+            log = log.connection_response
+            self.scid_dcid_map[log.scid] = log.dcid
+            self.cert_device.l2cap.SendConfigurationRequest(l2cap_cert_pb2.ConfigurationRequest(dcid=log.dcid,
+                                                                                              signal_id=log.signal_id+1))
+        log_event_handler.on(is_connection_response, handle_connection_response)
+
+        def handle_configuration_request(log):
+            log = log.configuration_request
+            if log.dcid not in self.scid_dcid_map:
+                return
+            dcid = self.scid_dcid_map[log.dcid]
+            self.cert_device.l2cap.SendConfigurationResponse(l2cap_cert_pb2.ConfigurationResponse(scid=dcid,
+                                                                                            signal_id=log.signal_id))
+        log_event_handler.on(is_configuration_request, handle_configuration_request)
+
+        def handle_disconnection_request(log):
+            log = log.disconnection_request
+            self.cert_device.l2cap.SendDisconnectionResponse(l2cap_cert_pb2.DisconnectionResponse(dcid=log.dcid,scid=log.scid,
+                                                                                            signal_id=log.signal_id))
+        log_event_handler.on(is_disconnection_request, handle_disconnection_request)
+
+        def handle_information_request(log):
+            log = log.information_request
+            self.cert_device.l2cap.SendInformationResponse(l2cap_cert_pb2.InformationResponse(type=log.type,
+                                                                                      signal_id=log.signal_id))
+        log_event_handler.on(is_information_request, handle_information_request)
+
+        self.event_dump = []
+        def dump_log(log):
+            self.event_dump.append(log)
+        log_event_handler.on(lambda _: True, dump_log)
+        self.event_handler = log_event_handler
+
     def teardown_test(self):
         self.device_under_test.rootservice.StopStack(
             facade_rootservice_pb2.StopStackRequest()
@@ -67,98 +157,103 @@
             cert_rootservice_pb2.StopStackRequest()
         )
 
+    def _setup_link(self):
+        self.cert_device.l2cap.SetupLink(l2cap_cert_pb2.SetupLinkRequest(remote=self.dut_address))
+        link_up_handled = []
+        def handle_link_up(log):
+            log = log.link_up
+            link_up_handled.append(log.remote)
+        self.event_handler.on(lambda log : log.HasField("link_up"), handle_link_up)
+        logs = self.cert_device.l2cap.FetchL2capLog(l2cap_cert_pb2.FetchL2capLogRequest())
+        self.event_handler.execute(logs)
+        assert self.dut_address in link_up_handled
+
+    def _open_channel(self, scid=0x0101, psm=0x01):
+        self.device_under_test.l2cap.SetDynamicChannel(l2cap_facade_pb2.SetEnableDynamicChannelRequest(psm=psm))
+
+        configuration_response_handled = []
+        def handle_configuration_response(log):
+            log = log.configuration_response
+            configuration_response_handled.append(log.scid)
+        self.event_handler.on(is_configuration_response, handle_configuration_response)
+        self.cert_device.l2cap.SendConnectionRequest(l2cap_cert_pb2.ConnectionRequest(scid=scid, psm=psm))
+        logs = self.cert_device.l2cap.FetchL2capLog(l2cap_cert_pb2.FetchL2capLogRequest())
+        self.event_handler.execute(logs)
+        assert scid in configuration_response_handled
+
+    def test_connect(self):
+        self._setup_link()
+        self._open_channel(scid=0x0101)
+
     def test_connect_and_send_data(self):
         self.device_under_test.l2cap.RegisterChannel(l2cap_facade_pb2.RegisterChannelRequest(channel=2))
         self.device_under_test.l2cap.SetDynamicChannel(l2cap_facade_pb2.SetEnableDynamicChannelRequest(psm=0x01))
-        dut_packet_stream = self.device_under_test.l2cap.packet_stream
-        cert_packet_stream = self.cert_device.l2cap.packet_stream
-        cert_connection_stream = self.cert_device.l2cap.connection_complete_stream
-        dut_connection_stream = self.device_under_test.l2cap.connection_complete_stream
-        cert_connection_stream.subscribe()
-        dut_connection_stream.subscribe()
-        self.device_under_test.l2cap.Connect(self.cert_address)
-        cert_connection_stream.assert_event_occurs(
-            lambda device: device.remote == self.dut_address
-        )
-        cert_connection_stream.unsubscribe()
-        dut_connection_stream.assert_event_occurs(
-            lambda device: device.remote == self.cert_address
-        )
-        dut_connection_stream.unsubscribe()
-
-        self.cert_device.l2cap.SendConnectionRequest(l2cap_cert_pb2.ConnectionRequest(scid=0x101, psm=1))
-        time.sleep(ASYNC_OP_TIME_SECONDS)
-        open_channels = self.cert_device.l2cap.FetchOpenedChannels(l2cap_cert_pb2.FetchOpenedChannelsRequest())
-        cid = open_channels.dcid[0]
-        self.cert_device.l2cap.SendConfigurationRequest(l2cap_cert_pb2.ConfigurationRequest(scid=cid))
-        time.sleep(ASYNC_OP_TIME_SECONDS)
-
-        dut_packet_stream.subscribe()
-        cert_packet_stream.subscribe()
-
-        self.cert_device.l2cap.SendL2capPacket(l2cap_facade_pb2.L2capPacket(channel=2, payload=b"abc"))
-        dut_packet_stream.assert_event_occurs(
-            lambda packet: b"abc" in packet.payload
-        )
+        self._setup_link()
+        scid = 0x0101
+        self._open_channel(scid=scid)
         self.device_under_test.l2cap.SendL2capPacket(l2cap_facade_pb2.L2capPacket(channel=2, payload=b"123"))
-        cert_packet_stream.assert_event_occurs(
-            lambda packet: b"123" in packet.payload
-        )
 
-        self.cert_device.l2cap.SendL2capPacket(l2cap_facade_pb2.L2capPacket(channel=cid, payload=b"123"))
-        dut_packet_stream.assert_event_occurs(
-            lambda packet: b"123" in packet.payload
-        )
+        data_received = []
+        event_handler = EventHandler()
+        def on_data_received(log):
+            log = log.data_packet
+            data_received.append((log.channel, log.payload))
+        event_handler.on(lambda log : log.HasField("data_packet"), on_data_received)
+        logs = self.cert_device.l2cap.FetchL2capLog(l2cap_cert_pb2.FetchL2capLogRequest())
+        event_handler.execute(logs)
+        assert (2, b"123") in data_received
 
         self.device_under_test.l2cap.SendDynamicChannelPacket(l2cap_facade_pb2.DynamicChannelPacket(psm=1, payload=b'abc'))
-        cert_packet_stream.assert_event_occurs(
-            lambda packet: b"abc" in packet.payload
-        )
-
-        self.cert_device.l2cap.SendDisconnectionRequest(l2cap_cert_pb2.DisconnectionRequest(dcid=0x40, scid=101))
-        time.sleep(ASYNC_OP_TIME_SECONDS)
-        dut_packet_stream.unsubscribe()
-        cert_packet_stream.unsubscribe()
+        logs = self.cert_device.l2cap.FetchL2capLog(l2cap_cert_pb2.FetchL2capLogRequest())
+        event_handler.execute(logs)
+        assert (scid, b"abc") in data_received
 
     def test_open_two_channels(self):
-        cert_connection_stream = self.cert_device.l2cap.connection_complete_stream
-        cert_connection_stream.subscribe()
-        self.device_under_test.l2cap.OpenChannel(l2cap_facade_pb2.OpenChannelRequest(remote=self.cert_address, psm=0x01))
-        self.device_under_test.l2cap.OpenChannel(l2cap_facade_pb2.OpenChannelRequest(remote=self.cert_address, psm=0x03))
-        cert_connection_stream.assert_event_occurs(
-            lambda device: device.remote == self.dut_address
-        )
-        cert_connection_stream.unsubscribe()
-        time.sleep(ASYNC_OP_TIME_SECONDS)
-        open_channels = self.cert_device.l2cap.FetchOpenedChannels(l2cap_cert_pb2.FetchOpenedChannelsRequest())
-        assert len(open_channels.dcid) == 2
+        self._setup_link()
+        self._open_channel(scid=0x0101, psm=0x1)
+        self._open_channel(scid=0x0102, psm=0x3)
 
     def test_accept_disconnect(self):
         """
         L2CAP/COS/CED/BV-07-C
         """
-        self.device_under_test.l2cap.OpenChannel(l2cap_facade_pb2.OpenChannelRequest(remote=self.cert_address, psm=0x01))
-        cert_connection_stream = self.cert_device.l2cap.connection_complete_stream
-        cert_connection_stream.subscribe()
-        self.device_under_test.l2cap.Connect(self.cert_address)
-        cert_connection_stream.assert_event_occurs(
-            lambda device: device.remote == self.dut_address
-        )
-        cert_connection_stream.unsubscribe()
-        time.sleep(ASYNC_OP_TIME_SECONDS)
-        cert_packet_stream = self.cert_device.l2cap.packet_stream
-        cert_packet_stream.subscribe()
-        open_channels = self.cert_device.l2cap.FetchOpenedChannels(l2cap_cert_pb2.FetchOpenedChannelsRequest())
-        cid = open_channels.dcid[0]
-        disconnection_request_packet = b"\x06\x01\x04\x00\x40\x00\x40\x01"
-        disconnection_response_packet = b"\x07\x01\x04\x00\x40\x00\x40\x01"
-        #TODO(b/143374372): Instead of hardcoding this, use packet builder
-        self.cert_device.l2cap.SendL2capPacket(l2cap_facade_pb2.L2capPacket(channel=1, payload=disconnection_request_packet))
-        cert_packet_stream.assert_event_occurs(
-            lambda packet: disconnection_response_packet in packet.payload
-        )
-        cert_packet_stream.unsubscribe()
-        time.sleep(ASYNC_OP_TIME_SECONDS)  # TODO(b/144186649): Remove this line
+        self._setup_link()
+        scid=0x0101
+        self._open_channel(scid=scid, psm=0x1)
+        dcid = self.scid_dcid_map[scid]
+        disconnection_response_handled = []
+        def handle_disconnection_response(log):
+            log = log.disconnection_response
+            disconnection_response_handled.append((log.scid, log.dcid))
+        self.event_handler.on(is_disconnection_response, handle_disconnection_response)
+        self.cert_device.l2cap.SendDisconnectionRequest(l2cap_cert_pb2.DisconnectionRequest(scid=scid, dcid=dcid, signal_id=2))
+        logs = self.cert_device.l2cap.FetchL2capLog(l2cap_cert_pb2.FetchL2capLogRequest())
+        self.event_handler.execute(logs)
+        assert (scid, dcid) in disconnection_response_handled
+
+    def test_disconnect_on_timeout(self):
+        """
+        L2CAP/COS/CED/BV-08-C
+        """
+        self._setup_link()
+        scid = 0x0101
+        psm = 1
+        self._open_channel(scid=0x0101, psm=0x1)
+
+        self.device_under_test.l2cap.SetDynamicChannel(l2cap_facade_pb2.SetEnableDynamicChannelRequest(psm=psm))
+
+        # Don't send configuration response back
+        self.event_handler.on(is_configuration_request, lambda _: True)
+        self.cert_device.l2cap.SendConnectionRequest(l2cap_cert_pb2.ConnectionRequest(scid=scid, psm=psm))
+        logs = self.cert_device.l2cap.FetchL2capLog(l2cap_cert_pb2.FetchL2capLogRequest())
+        self.event_handler.execute(logs)
+        time.sleep(3)
+        def handle_configuration_response(log):
+            # DUT should not send configuration response due to timeout
+            assert False
+        self.event_handler.on(is_configuration_response, handle_configuration_response)
+        logs = self.cert_device.l2cap.FetchL2capLog(l2cap_cert_pb2.FetchL2capLogRequest())
+        self.event_handler.execute(logs)
 
     def test_basic_operation_request_connection(self):
         """
@@ -166,58 +261,67 @@
         Verify that the IUT is able to request the connection establishment for an L2CAP data channel and
         initiate the configuration procedure.
         """
-        cert_connection_stream = self.cert_device.l2cap.connection_complete_stream
-        cert_connection_stream.subscribe()
-        self.device_under_test.l2cap.OpenChannel(l2cap_facade_pb2.OpenChannelRequest(remote=self.cert_address, psm=0x01))
-        cert_connection_stream.assert_event_occurs(
-            lambda device: device.remote == self.dut_address
-        )
-        cert_connection_stream.unsubscribe()
+        psm = 1
+        self.device_under_test.l2cap.OpenChannel(l2cap_facade_pb2.OpenChannelRequest(remote=self.cert_address, psm=psm))
+        connection_request = []
+        def handle_connection_request(log):
+            log = log.connection_request
+            connection_request.append(log.psm)
+        self.event_handler.on(is_connection_request, handle_connection_request)
+        logs = self.cert_device.l2cap.FetchL2capLog(l2cap_cert_pb2.FetchL2capLogRequest())
+        self.event_handler.execute(logs)
+        assert psm in connection_request
 
     def test_respond_to_echo_request(self):
         """
         L2CAP/COS/ECH/BV-01-C [Respond to Echo Request]
         Verify that the IUT responds to an echo request.
         """
-        self.device_under_test.l2cap.RegisterChannel(l2cap_facade_pb2.RegisterChannelRequest(channel=2))
-        cert_connection_stream = self.cert_device.l2cap.connection_complete_stream
-        cert_connection_stream.subscribe()
-        self.device_under_test.l2cap.Connect(self.cert_address)
-        cert_connection_stream.assert_event_occurs(
-            lambda device: device.remote == self.dut_address
-        )
-        cert_connection_stream.unsubscribe()
-        cert_packet_stream = self.cert_device.l2cap.packet_stream
-        cert_packet_stream.subscribe()
+        self._setup_link()
+        # TODO: Replace with constructed packets when PDL is available
         echo_request_packet = b"\x08\x01\x00\x00"
-        echo_response_packet = b"\x09\x01\x00\x00"
         self.cert_device.l2cap.SendL2capPacket(l2cap_facade_pb2.L2capPacket(channel=1, payload=echo_request_packet))
-        cert_packet_stream.assert_event_occurs(
-            lambda packet: echo_response_packet in packet.payload
-        )
-        cert_packet_stream.unsubscribe()
-        time.sleep(ASYNC_OP_TIME_SECONDS)  # TODO(b/144186649): Remove this line
+        echo_response = []
+        def handle_echo_response(log):
+            log = log.echo_response
+            echo_response.append(log.signal_id)
+        self.event_handler.on(is_echo_response, handle_echo_response)
+        logs = self.cert_device.l2cap.FetchL2capLog(l2cap_cert_pb2.FetchL2capLogRequest())
+        self.event_handler.execute(logs)
+        assert 0x01 in echo_response
 
     def test_reject_unknown_command(self):
         """
         L2CAP/COS/CED/BI-01-C
         """
-        cert_connection_stream = self.cert_device.l2cap.connection_complete_stream
-        cert_connection_stream.subscribe()
-        self.device_under_test.l2cap.RegisterChannel(l2cap_facade_pb2.RegisterChannelRequest(channel=2))
-        self.device_under_test.l2cap.Connect(self.cert_address)
-        cert_connection_stream.assert_event_occurs(
-            lambda device: device.remote == self.dut_address
-        )
-        cert_connection_stream.unsubscribe()
-        cert_packet_stream = self.cert_device.l2cap.packet_stream
-        cert_packet_stream.subscribe()
+        self._setup_link()
+        # TODO: Replace with constructed packets when PDL is available
         invalid_command_packet = b"\xff\x01\x00\x00"
         self.cert_device.l2cap.SendL2capPacket(l2cap_facade_pb2.L2capPacket(channel=1, payload=invalid_command_packet))
         command_reject_packet = b"\x01\x01\x02\x00\x00\x00"
-        cert_packet_stream.assert_event_occurs(
-            lambda packet: command_reject_packet in packet.payload
-        )
-        cert_packet_stream.unsubscribe()
+        command_reject = []
+        def handle_command_reject(log):
+            log = log.command_reject
+            command_reject.append(log.signal_id)
+        self.event_handler.on(is_command_reject, handle_command_reject)
+        logs = self.cert_device.l2cap.FetchL2capLog(l2cap_cert_pb2.FetchL2capLogRequest())
+        self.event_handler.execute(logs)
+        assert 0x01 in command_reject
 
-        time.sleep(ASYNC_OP_TIME_SECONDS)  # TODO(b/144186649): Remove this line
+    def test_query_for_1_2_features(self):
+        """
+        L2CAP/COS/IEX/BV-01-C [Query for 1.2 Features]
+        """
+        self._setup_link()
+        signal_id = 3
+        self.cert_device.l2cap.SendInformationRequest(
+            l2cap_cert_pb2.InformationRequest(
+                type=l2cap_cert_pb2.InformationRequestType.FIXED_CHANNELS, signal_id=signal_id))
+        info_response = []
+        def handle_info_response(log):
+            log = log.information_response
+            info_response.append((log.signal_id, log.type))
+        self.event_handler.on(is_information_response, handle_info_response)
+        logs = self.cert_device.l2cap.FetchL2capLog(l2cap_cert_pb2.FetchL2capLogRequest())
+        self.event_handler.execute(logs)
+        assert (signal_id, l2cap_cert_pb2.InformationRequestType.FIXED_CHANNELS) in info_response
diff --git a/gd/l2cap/classic/internal/signalling_manager.cc b/gd/l2cap/classic/internal/signalling_manager.cc
index dcdce54..fb1cb05 100644
--- a/gd/l2cap/classic/internal/signalling_manager.cc
+++ b/gd/l2cap/classic/internal/signalling_manager.cc
@@ -454,7 +454,22 @@
 
 void ClassicSignallingManager::on_command_timeout() {
   LOG_WARN("Response time out");
-  link_->OnAclDisconnected(hci::ErrorCode::SUCCESS);
+  if (pending_commands_.empty()) {
+    LOG_ERROR("No pending command");
+    return;
+  }
+
+  auto last_sent_command = std::move(pending_commands_.front());
+  pending_commands_.pop();
+  switch (last_sent_command.command_code_) {
+    case CommandCode::CONFIGURATION_REQUEST: {
+      SendDisconnectionRequest(last_sent_command.source_cid_, last_sent_command.destination_cid_);
+      break;
+    }
+    default:
+      break;
+  }
+  handle_send_next_command();
 }
 
 void ClassicSignallingManager::handle_send_next_command() {