Split packets in IPC-friendly-sized slices after filtering

Client side filtering creates reassembles the packet into one big
slice. If the slice is too large (larger than ipc::kIPCBufferSize), the
packet will hit an assertion later on.

Changing the MessageFilter to avoid creating big slices would be a nicer
fix. We should probably do that in the future.

Bug: 195065199
Change-Id: Id6323e8a64d4ff71d40ec902717069153b2cd23d
diff --git a/Android.bp b/Android.bp
index 85087bb..46db7fe 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1074,6 +1074,7 @@
         ":perfetto_src_ipc_perfetto_ipc",
         ":perfetto_src_kallsyms_kallsyms",
         ":perfetto_src_protozero_filtering_bytecode_common",
+        ":perfetto_src_protozero_filtering_bytecode_generator",
         ":perfetto_src_protozero_filtering_bytecode_parser",
         ":perfetto_src_protozero_filtering_message_filter",
         ":perfetto_src_protozero_protozero",
@@ -1739,6 +1740,7 @@
         ":perfetto_src_profiling_memory_scoped_spinlock",
         ":perfetto_src_profiling_memory_wire_protocol",
         ":perfetto_src_protozero_filtering_bytecode_common",
+        ":perfetto_src_protozero_filtering_bytecode_generator",
         ":perfetto_src_protozero_filtering_bytecode_parser",
         ":perfetto_src_protozero_filtering_message_filter",
         ":perfetto_src_protozero_protozero",
diff --git a/src/tracing/core/tracing_service_impl.cc b/src/tracing/core/tracing_service_impl.cc
index 9ca22d8..114000d 100644
--- a/src/tracing/core/tracing_service_impl.cc
+++ b/src/tracing/core/tracing_service_impl.cc
@@ -287,6 +287,29 @@
   PERFETTO_FATAL("For GCC");
 }
 
+// Appends `data` (which has `size` bytes), to `*packet`. Splits the data in
+// slices no larger than `max_slice_size`.
+void AppendOwnedSlicesToPacket(std::unique_ptr<uint8_t[]> data,
+                               size_t size,
+                               size_t max_slice_size,
+                               perfetto::TracePacket* packet) {
+  if (size <= max_slice_size) {
+    packet->AddSlice(Slice::TakeOwnership(std::move(data), size));
+    return;
+  }
+  uint8_t* src_ptr = data.get();
+  for (size_t size_left = size; size_left > 0;) {
+    const size_t slice_size = std::min(size_left, max_slice_size);
+
+    Slice slice = Slice::Allocate(slice_size);
+    memcpy(slice.own_data(), src_ptr, slice_size);
+    packet->AddSlice(std::move(slice));
+
+    src_ptr += slice_size;
+    size_left -= slice_size;
+  }
+}
+
 }  // namespace
 
 // These constants instead are defined in the header because are used by tests.
@@ -2168,8 +2191,9 @@
         continue;
       }
       tracing_session->filter_output_bytes += filtered_packet.size;
-      it->AddSlice(Slice::TakeOwnership(std::move(filtered_packet.data),
-                                        filtered_packet.size));
+      AppendOwnedSlicesToPacket(std::move(filtered_packet.data),
+                                filtered_packet.size, kMaxTracePacketSliceSize,
+                                &*it);
 
     }  // for (packet)
   }    // if (trace_filter)
diff --git a/src/tracing/core/tracing_service_impl.h b/src/tracing/core/tracing_service_impl.h
index 401aaa1..8c50713 100644
--- a/src/tracing/core/tracing_service_impl.h
+++ b/src/tracing/core/tracing_service_impl.h
@@ -77,6 +77,9 @@
   static constexpr uint8_t kSyncMarker[] = {0x82, 0x47, 0x7a, 0x76, 0xb2, 0x8d,
                                             0x42, 0xba, 0x81, 0xdc, 0x33, 0x32,
                                             0x6d, 0x57, 0xa0, 0x79};
+  static constexpr size_t kMaxTracePacketSliceSize =
+      128 * 1024 - 512;  // This is ipc::kIPCBufferSize - 512, see assertion in
+                         // tracing_integration_test.cc and b/195065199
 
   // The implementation behind the service endpoint exposed to each producer.
   class ProducerEndpointImpl : public TracingService::ProducerEndpoint {
diff --git a/src/tracing/test/tracing_integration_test.cc b/src/tracing/test/tracing_integration_test.cc
index 134ffd0..d1ca340 100644
--- a/src/tracing/test/tracing_integration_test.cc
+++ b/src/tracing/test/tracing_integration_test.cc
@@ -113,6 +113,11 @@
   EXPECT_EQ(0u, buf_stats.abi_violations());
 }
 
+static_assert(TracingServiceImpl::kMaxTracePacketSliceSize <=
+                  ipc::kIPCBufferSize - 512,
+              "Tracing service max packet slice should be smaller than IPC "
+              "buffer size (with some headroom)");
+
 }  // namespace
 
 class TracingIntegrationTest : public ::testing::Test {
diff --git a/test/BUILD.gn b/test/BUILD.gn
index 64e683a..01ae3c6 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -41,6 +41,7 @@
     ]
     if (enable_perfetto_traced_probes) {
       deps += [
+        "../src/protozero/filtering:bytecode_generator",
         "../src/traced/probes/ftrace",
         "../src/traced/probes/ftrace:ftrace_procfs",
       ]
diff --git a/test/end_to_end_integrationtest.cc b/test/end_to_end_integrationtest.cc
index 75cfa88..1ec13d3 100644
--- a/test/end_to_end_integrationtest.cc
+++ b/test/end_to_end_integrationtest.cc
@@ -41,6 +41,7 @@
 #include "perfetto/tracing/core/tracing_service_state.h"
 #include "src/base/test/test_task_runner.h"
 #include "src/base/test/utils.h"
+#include "src/protozero/filtering/filter_bytecode_generator.h"
 #include "src/traced/probes/ftrace/ftrace_controller.h"
 #include "src/traced/probes/ftrace/ftrace_procfs.h"
 #include "test/gtest_and_gmock.h"
@@ -74,8 +75,11 @@
 namespace {
 
 using ::testing::ContainsRegex;
+using ::testing::Each;
 using ::testing::ElementsAreArray;
 using ::testing::HasSubstr;
+using ::testing::Property;
+using ::testing::SizeIs;
 
 std::string RandomTraceFileName() {
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
@@ -1125,6 +1129,67 @@
   }
 }
 
+// Regression test for b/195065199. Check that trace filtering works when a
+// packet size exceeds the IPC limit. This tests that the tracing service, when
+// reassembling packets after filtering, doesn't "overglue" them. They still
+// need to be slice-able to fit into the ReadBuffers ipc.
+TEST_F(PerfettoTest, TraceFilterLargePackets) {
+  base::TestTaskRunner task_runner;
+  TestHelper helper(&task_runner);
+
+  helper.StartServiceIfRequired();
+  helper.ConnectFakeProducer();
+  helper.ConnectConsumer();
+  helper.WaitForConsumerConnect();
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(1024 * 16);
+  trace_config.set_duration_ms(500);
+  auto* prod_config = trace_config.add_producers();
+  prod_config->set_producer_name("android.perfetto.FakeProducer");
+  prod_config->set_shm_size_kb(1024 * 16);
+  prod_config->set_page_size_kb(32);
+
+  static constexpr size_t kNumPackets = 3;
+  static constexpr uint32_t kRandomSeed = 42;
+  static constexpr uint32_t kMsgSize = 8 * ipc::kIPCBufferSize;
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("android.perfetto.FakeProducer");
+  auto* test_config = ds_config->mutable_for_testing();
+  test_config->set_seed(kRandomSeed);
+  test_config->set_message_count(kNumPackets);
+  test_config->set_message_size(kMsgSize);
+  test_config->set_send_batch_on_register(true);
+
+  protozero::FilterBytecodeGenerator filt;
+  // Message 0: root Trace proto.
+  filt.AddNestedField(1 /* root trace.packet*/, 1);
+  filt.EndMessage();
+
+  // Message 1: TracePacket proto. Allow all fields.
+  filt.AddSimpleFieldRange(1, 1000);
+  filt.EndMessage();
+
+  trace_config.mutable_trace_filter()->set_bytecode(filt.Serialize());
+
+  // The data source is configured to emit another batch when it is started via
+  // send_batch_on_register in the TestConfig.
+  helper.StartTracing(trace_config);
+  helper.WaitForTracingDisabled();
+
+  helper.ReadData();
+  helper.WaitForReadData(/* read_count */ 0, /* timeout_ms */ 10000);
+
+  const std::vector<protos::gen::TracePacket>& packets = helper.trace();
+  EXPECT_EQ(packets.size(), kNumPackets);
+  EXPECT_THAT(packets,
+              Each(Property(&protos::gen::TracePacket::has_for_testing, true)));
+  EXPECT_THAT(
+      packets,
+      Each(Property(&protos::gen::TracePacket::for_testing,
+                    Property(&protos::gen::TestEvent::str, SizeIs(kMsgSize)))));
+}
+
 // Disable cmdline tests on sanitizets because they use fork() and that messes
 // up leak / races detections, which has been fixed only recently (see
 // https://github.com/google/sanitizers/issues/836 ).