Add test for the NetworkTraceHandler

This tests the logic of converting bpf PacketTraces into Perfetto
TracePackets. The test stands up an in-process trace and parses the
resulting trace output (filtering to only the events we care about).

On the implementation side, `Write` is added as a non-static method
to do the conversion from a batch of PacketTraces. Because this becomes
part of the instance, per-instance configuration (e.g. interning) can be
applied without causing conflicts between concurrent sessions.

Bug: 246985031
Test: atest libnetworkstats_test
Change-Id: I15a26ba720eff308d01e6827a176a6b2f7c60e80
diff --git a/service-t/native/libs/libnetworkstats/Android.bp b/service-t/native/libs/libnetworkstats/Android.bp
index f2c569f..f40d388 100644
--- a/service-t/native/libs/libnetworkstats/Android.bp
+++ b/service-t/native/libs/libnetworkstats/Android.bp
@@ -62,6 +62,7 @@
     header_libs: ["bpf_connectivity_headers"],
     srcs: [
         "BpfNetworkStatsTest.cpp",
+        "NetworkTraceHandlerTest.cpp",
         "NetworkTracePollerTest.cpp",
     ],
     cflags: [
@@ -74,6 +75,8 @@
         "libgmock",
         "libnetworkstats",
         "libperfetto_client_experimental",
+        "libprotobuf-cpp-lite",
+        "perfetto_trace_protos",
     ],
     shared_libs: [
         "libbase",
diff --git a/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp b/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp
index 07f8054..cd62bc5 100644
--- a/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp
+++ b/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp
@@ -59,10 +59,12 @@
 // static
 NetworkTracePoller NetworkTraceHandler::sPoller(
     [](const std::vector<PacketTrace>& packets) {
+      // Trace calls the provided callback for each active session. The context
+      // gets a reference to the NetworkTraceHandler instance associated with
+      // the session and delegates writing. The corresponding handler will write
+      // with the setting specified in the trace config.
       NetworkTraceHandler::Trace([&](NetworkTraceHandler::TraceContext ctx) {
-        for (const PacketTrace& pkt : packets) {
-          NetworkTraceHandler::Fill(pkt, *ctx.NewTracePacket());
-        }
+        ctx.GetDataSourceLocked()->Write(packets, ctx);
       });
     });
 
@@ -92,6 +94,13 @@
   mStarted = false;
 }
 
+void NetworkTraceHandler::Write(const std::vector<PacketTrace>& packets,
+                                NetworkTraceHandler::TraceContext& ctx) {
+  for (const PacketTrace& pkt : packets) {
+    Fill(pkt, *ctx.NewTracePacket());
+  }
+}
+
 // static class method
 void NetworkTraceHandler::Fill(const PacketTrace& src, TracePacket& dst) {
   dst.set_timestamp(src.timestampNs);
diff --git a/service-t/native/libs/libnetworkstats/NetworkTraceHandlerTest.cpp b/service-t/native/libs/libnetworkstats/NetworkTraceHandlerTest.cpp
new file mode 100644
index 0000000..2318da5
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/NetworkTraceHandlerTest.cpp
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2023 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <vector>
+
+#include "netdbpf/NetworkTraceHandler.h"
+#include "protos/perfetto/trace/android/network_trace.pb.h"
+#include "protos/perfetto/trace/trace.pb.h"
+#include "protos/perfetto/trace/trace_packet.pb.h"
+
+namespace android {
+namespace bpf {
+using ::perfetto::protos::NetworkPacketEvent;
+using ::perfetto::protos::Trace;
+using ::perfetto::protos::TracePacket;
+using ::perfetto::protos::TrafficDirection;
+
+// This handler makes OnStart and OnStop a no-op so that tracing is not really
+// started on the device.
+class HandlerForTest : public NetworkTraceHandler {
+ public:
+  void OnStart(const StartArgs&) override {}
+  void OnStop(const StopArgs&) override {}
+};
+
+class NetworkTraceHandlerTest : public testing::Test {
+ protected:
+  // Starts a tracing session with the handler under test.
+  std::unique_ptr<perfetto::TracingSession> StartTracing() {
+    perfetto::TracingInitArgs args;
+    args.backends = perfetto::kInProcessBackend;
+    perfetto::Tracing::Initialize(args);
+
+    perfetto::DataSourceDescriptor dsd;
+    dsd.set_name("test.network_packets");
+    HandlerForTest::Register(dsd);
+
+    perfetto::TraceConfig cfg;
+    cfg.add_buffers()->set_size_kb(1024);
+    cfg.add_data_sources()->mutable_config()->set_name("test.network_packets");
+
+    auto session = perfetto::Tracing::NewTrace(perfetto::kInProcessBackend);
+    session->Setup(cfg);
+    session->StartBlocking();
+    return session;
+  }
+
+  // Stops the trace session and reports all relevant trace packets.
+  bool StopTracing(perfetto::TracingSession* session,
+                   std::vector<TracePacket>* output) {
+    session->StopBlocking();
+
+    Trace trace;
+    std::vector<char> raw_trace = session->ReadTraceBlocking();
+    if (!trace.ParseFromArray(raw_trace.data(), raw_trace.size())) {
+      ADD_FAILURE() << "trace.ParseFromArray failed";
+      return false;
+    }
+
+    // This is a real trace and includes irrelevant trace packets such as trace
+    // metadata. The following strips the results to just the packets we want.
+    for (const auto& pkt : trace.packet()) {
+      if (pkt.has_network_packet()) {
+        output->emplace_back(pkt);
+      }
+    }
+
+    return true;
+  }
+
+  // This runs a trace with a single call to Write.
+  bool TraceAndSortPackets(const std::vector<PacketTrace>& input,
+                           std::vector<TracePacket>* output) {
+    auto session = StartTracing();
+    HandlerForTest::Trace([&](HandlerForTest::TraceContext ctx) {
+      ctx.GetDataSourceLocked()->Write(input, ctx);
+      ctx.Flush();
+    });
+
+    if (!StopTracing(session.get(), output)) {
+      return false;
+    }
+
+    // Sort to provide deterministic ordering regardless of Perfetto internals
+    // or implementation-defined (e.g. hash map) reshuffling.
+    std::sort(output->begin(), output->end(),
+              [](const TracePacket& a, const TracePacket& b) {
+                return a.timestamp() < b.timestamp();
+              });
+
+    return true;
+  }
+};
+
+TEST_F(NetworkTraceHandlerTest, WriteBasicFields) {
+  std::vector<PacketTrace> input = {
+      PacketTrace{
+          .timestampNs = 1000,
+          .length = 100,
+          .uid = 10,
+          .tag = 123,
+          .ipProto = 6,
+          .tcpFlags = 1,
+      },
+  };
+
+  std::vector<TracePacket> events;
+  ASSERT_TRUE(TraceAndSortPackets(input, &events));
+
+  ASSERT_EQ(events.size(), 1);
+  EXPECT_THAT(events[0].timestamp(), 1000);
+  EXPECT_THAT(events[0].network_packet().length(), 100);
+  EXPECT_THAT(events[0].network_packet().uid(), 10);
+  EXPECT_THAT(events[0].network_packet().tag(), 123);
+  EXPECT_THAT(events[0].network_packet().ip_proto(), 6);
+  EXPECT_THAT(events[0].network_packet().tcp_flags(), 1);
+}
+
+TEST_F(NetworkTraceHandlerTest, WriteDirectionAndPorts) {
+  std::vector<PacketTrace> input = {
+      PacketTrace{
+          .timestampNs = 1,
+          .sport = htons(8080),
+          .dport = htons(443),
+          .egress = true,
+      },
+      PacketTrace{
+          .timestampNs = 2,
+          .sport = htons(443),
+          .dport = htons(8080),
+          .egress = false,
+      },
+  };
+
+  std::vector<TracePacket> events;
+  ASSERT_TRUE(TraceAndSortPackets(input, &events));
+
+  ASSERT_EQ(events.size(), 2);
+  EXPECT_THAT(events[0].network_packet().local_port(), 8080);
+  EXPECT_THAT(events[0].network_packet().remote_port(), 443);
+  EXPECT_THAT(events[0].network_packet().direction(),
+              TrafficDirection::DIR_EGRESS);
+  EXPECT_THAT(events[1].network_packet().local_port(), 8080);
+  EXPECT_THAT(events[1].network_packet().remote_port(), 443);
+  EXPECT_THAT(events[1].network_packet().direction(),
+              TrafficDirection::DIR_INGRESS);
+}
+
+}  // namespace bpf
+}  // namespace android
diff --git a/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h b/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h
index bdc18fb..9520d7b 100644
--- a/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h
+++ b/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h
@@ -46,6 +46,11 @@
   void OnStart(const StartArgs&) override;
   void OnStop(const StopArgs&) override;
 
+  // Writes the packets as Perfetto TracePackets, creating packets as needed
+  // using the provided callback (which allows easy testing).
+  void Write(const std::vector<PacketTrace>& packets,
+             NetworkTraceHandler::TraceContext& ctx);
+
  private:
   // Convert a PacketTrace into a Perfetto trace packet.
   static void Fill(const PacketTrace& src,