1 /*
2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "src/trace_redaction/collect_frame_cookies.h"
18
19 #include "perfetto/base/status.h"
20 #include "perfetto/protozero/field.h"
21 #include "perfetto/protozero/proto_decoder.h"
22 #include "perfetto/protozero/scattered_heap_buffer.h"
23 #include "src/trace_redaction/proto_util.h"
24 #include "src/trace_redaction/trace_redaction_framework.h"
25
26 #include "protos/perfetto/trace/android/frame_timeline_event.pbzero.h"
27 #include "protos/perfetto/trace/trace_packet.pbzero.h"
28
29 namespace perfetto::trace_redaction {
30
31 namespace {
32
33 using FrameTimelineEvent = protos::pbzero::FrameTimelineEvent;
34
35 struct Frame {
36 uint32_t id;
37 uint32_t pid;
38 uint32_t cookie;
39 };
40
41 constexpr Frame kActualDisplayFrameStart = {
42 FrameTimelineEvent::kActualDisplayFrameStartFieldNumber,
43 FrameTimelineEvent::ActualDisplayFrameStart::kPidFieldNumber,
44 FrameTimelineEvent::ActualDisplayFrameStart::kCookieFieldNumber,
45 };
46
47 constexpr Frame kExpectedDisplayFrameStart = {
48 FrameTimelineEvent::kExpectedDisplayFrameStartFieldNumber,
49 FrameTimelineEvent::ExpectedDisplayFrameStart::kPidFieldNumber,
50 FrameTimelineEvent::ExpectedDisplayFrameStart::kCookieFieldNumber,
51 };
52
53 constexpr Frame kActualSurfaceFrameStart = {
54 FrameTimelineEvent::kActualSurfaceFrameStartFieldNumber,
55 FrameTimelineEvent::ActualSurfaceFrameStart::kPidFieldNumber,
56 FrameTimelineEvent::ActualSurfaceFrameStart::kCookieFieldNumber,
57 };
58
59 constexpr Frame kExpectedSurfaceFrameStart = {
60 FrameTimelineEvent::kExpectedSurfaceFrameStartFieldNumber,
61 FrameTimelineEvent::ExpectedSurfaceFrameStart::kPidFieldNumber,
62 FrameTimelineEvent::ExpectedSurfaceFrameStart::kCookieFieldNumber,
63 };
64
65 // Do not use `pid` from `kFrameEnd`.
66 constexpr Frame kFrameEnd = {
67 FrameTimelineEvent::kFrameEndFieldNumber,
68 0,
69 FrameTimelineEvent::FrameEnd::kCookieFieldNumber,
70 };
71
72 } // namespace
73
Begin(Context * context) const74 base::Status CollectFrameCookies::Begin(Context* context) const {
75 if (context->global_frame_cookies.empty()) {
76 return base::OkStatus();
77 }
78
79 return base::ErrStatus("FindFrameCookies: frame cookies already populated");
80 }
81
Collect(const protos::pbzero::TracePacket::Decoder & packet,Context * context) const82 base::Status CollectFrameCookies::Collect(
83 const protos::pbzero::TracePacket::Decoder& packet,
84 Context* context) const {
85 // A frame cookie needs a time and pid for a timeline query. Ignore packets
86 // without a timestamp.
87 if (!packet.has_timestamp() || !packet.has_frame_timeline_event()) {
88 return base::OkStatus();
89 }
90
91 auto timestamp = packet.timestamp();
92
93 // Only use the start frames. They are the only ones with a pid. End events
94 // use the cookies to reference the pid in a start event.
95 auto handlers = {
96 kActualDisplayFrameStart,
97 kActualSurfaceFrameStart,
98 kExpectedDisplayFrameStart,
99 kExpectedSurfaceFrameStart,
100 };
101
102 // Timeline Event Decoder.
103 protozero::ProtoDecoder decoder(packet.frame_timeline_event());
104
105 // If no handler worked, cookie will not get added to the global cookie field.
106 for (const auto& handler : handlers) {
107 auto outer = decoder.FindField(handler.id);
108
109 if (!outer.valid()) {
110 continue;
111 }
112
113 protozero::ProtoDecoder inner(outer.as_bytes());
114
115 auto pid = inner.FindField(handler.pid);
116 auto cookie = inner.FindField(handler.cookie);
117
118 // This should be handled, but it is not valid. Drop the event by not adding
119 // it to the global_frame_cookies list.
120 if (!pid.valid() || !cookie.valid()) {
121 continue;
122 }
123
124 FrameCookie frame_cookie;
125 frame_cookie.pid = pid.as_int32();
126 frame_cookie.cookie = cookie.as_int64();
127 frame_cookie.ts = timestamp;
128
129 context->global_frame_cookies.push_back(frame_cookie);
130
131 break;
132 }
133
134 return base::OkStatus();
135 }
136
Build(Context * context) const137 base::Status ReduceFrameCookies::Build(Context* context) const {
138 if (!context->package_uid.has_value()) {
139 return base::ErrStatus("ReduceFrameCookies: missing package uid.");
140 }
141
142 if (!context->timeline) {
143 return base::ErrStatus("ReduceFrameCookies: missing timeline.");
144 }
145
146 // Even though it is rare, it is possible for there to be no SurfaceFlinger
147 // frame cookies. Even through the main path handles this, we use this early
148 // exit to document this edge case.
149 if (context->global_frame_cookies.empty()) {
150 return base::OkStatus();
151 }
152
153 // Filter the global cookies down to cookies that belong to the target package
154 // (uid).
155 for (const auto& cookie : context->global_frame_cookies) {
156 if (context->timeline->PidConnectsToUid(cookie.ts, cookie.pid,
157 *context->package_uid)) {
158 context->package_frame_cookies.insert(cookie.cookie);
159 }
160 }
161
162 return base::OkStatus();
163 }
164
Transform(const Context & context,std::string * packet) const165 base::Status FilterFrameEvents::Transform(const Context& context,
166 std::string* packet) const {
167 if (packet == nullptr || packet->empty()) {
168 return base::ErrStatus("FilterFrameEvents: null or empty packet.");
169 }
170
171 protozero::ProtoDecoder decoder(*packet);
172
173 if (!decoder
174 .FindField(
175 protos::pbzero::TracePacket::kFrameTimelineEventFieldNumber)
176 .valid()) {
177 return base::OkStatus();
178 }
179
180 protozero::HeapBuffered<protos::pbzero::TracePacket> message;
181
182 for (auto field = decoder.ReadField(); field.valid();
183 field = decoder.ReadField()) {
184 if (field.id() ==
185 protos::pbzero::TracePacket::kFrameTimelineEventFieldNumber) {
186 if (KeepField(context, field)) {
187 proto_util::AppendField(field, message.get());
188 }
189
190 } else {
191 proto_util::AppendField(field, message.get());
192 }
193 }
194
195 packet->assign(message.SerializeAsString());
196 return base::OkStatus();
197 }
198
KeepField(const Context & context,const protozero::Field & field) const199 bool FilterFrameEvents::KeepField(const Context& context,
200 const protozero::Field& field) const {
201 PERFETTO_DCHECK(field.id() ==
202 protos::pbzero::TracePacket::kFrameTimelineEventFieldNumber);
203
204 protozero::ProtoDecoder timeline_event_decoder(field.as_bytes());
205
206 auto handlers = {
207 kActualDisplayFrameStart,
208 kActualSurfaceFrameStart,
209 kExpectedDisplayFrameStart,
210 kExpectedSurfaceFrameStart,
211 kFrameEnd,
212 };
213
214 const auto& cookies = context.package_frame_cookies;
215
216 for (const auto& handler : handlers) {
217 auto event = timeline_event_decoder.FindField(handler.id);
218
219 if (!event.valid()) {
220 continue;
221 }
222
223 protozero::ProtoDecoder event_decoder(event.as_bytes());
224
225 auto cookie = event_decoder.FindField(handler.cookie);
226
227 if (cookie.valid() && cookies.count(cookie.as_int64())) {
228 return true;
229 }
230 }
231
232 return false;
233 }
234
235 } // namespace perfetto::trace_redaction
236