Coverage Report

Created: 2026-09-14 06:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/perfetto/src/ipc/buffered_frame_deserializer.cc
Line
Count
Source
1
/*
2
 * Copyright (C) 2017 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/ipc/buffered_frame_deserializer.h"
18
19
#include <algorithm>
20
#include <cinttypes>
21
#include <type_traits>
22
#include <utility>
23
24
#include "perfetto/base/logging.h"
25
#include "perfetto/ext/base/utils.h"
26
27
#include "protos/perfetto/ipc/wire_protocol.gen.h"
28
29
namespace perfetto {
30
namespace ipc {
31
32
namespace {
33
34
// The header is just the number of bytes of the Frame protobuf message.
35
constexpr size_t kHeaderSize = sizeof(uint32_t);
36
}  // namespace
37
38
BufferedFrameDeserializer::BufferedFrameDeserializer(size_t max_capacity)
39
8.44k
    : capacity_(max_capacity) {
40
8.44k
  PERFETTO_CHECK(max_capacity % base::GetSysPageSize() == 0);
41
8.44k
  PERFETTO_CHECK(max_capacity >= base::GetSysPageSize());
42
8.44k
}
43
44
8.44k
BufferedFrameDeserializer::~BufferedFrameDeserializer() = default;
45
46
BufferedFrameDeserializer::ReceiveBuffer
47
14.8k
BufferedFrameDeserializer::BeginReceive() {
48
  // Upon the first recv initialize the buffer to the max message size but
49
  // release the physical memory for all but the first page. The kernel will
50
  // automatically give us physical pages back as soon as we page-fault on them.
51
14.8k
  if (!buf_.IsValid()) {
52
8.16k
    PERFETTO_DCHECK(size_ == 0);
53
    // TODO(eseckler): Don't commit all of the buffer at once on Windows.
54
8.16k
    buf_ = base::PagedMemory::Allocate(capacity_);
55
56
    // Surely we are going to use at least the first page, but we may not need
57
    // the rest for a bit.
58
8.16k
    const auto page_size = base::GetSysPageSize();
59
8.16k
    buf_.AdviseDontNeed(buf() + page_size, capacity_ - page_size);
60
8.16k
  }
61
62
14.8k
  PERFETTO_CHECK(capacity_ > size_);
63
14.8k
  return ReceiveBuffer{buf() + size_, capacity_ - size_};
64
14.8k
}
65
66
14.8k
bool BufferedFrameDeserializer::EndReceive(size_t recv_size) {
67
14.8k
  const auto page_size = base::GetSysPageSize();
68
14.8k
  PERFETTO_CHECK(recv_size + size_ <= capacity_);
69
14.8k
  size_ += recv_size;
70
71
  // At this point the contents buf_ can contain:
72
  // A) Only a fragment of the header (the size of the frame). E.g.,
73
  //    03 00 00 (the header is 4 bytes, one is missing).
74
  //
75
  // B) A header and a part of the frame. E.g.,
76
  //     05 00 00 00         11 22 33
77
  //    [ header, size=5 ]  [ Partial frame ]
78
  //
79
  // C) One or more complete header+frame. E.g.,
80
  //     05 00 00 00         11 22 33 44 55   03 00 00 00        AA BB CC
81
  //    [ header, size=5 ]  [ Whole frame ]  [ header, size=3 ] [ Whole frame ]
82
  //
83
  // D) Some complete header+frame(s) and a partial header or frame (C + A/B).
84
  //
85
  // C Is the more likely case and the one we are optimizing for. A, B, D can
86
  // happen because of the streaming nature of the socket.
87
  // The invariant of this function is that, when it returns, buf_ is either
88
  // empty (we drained all the complete frames) or starts with the header of the
89
  // next, still incomplete, frame.
90
91
14.8k
  size_t consumed_size = 0;
92
4.85M
  for (;;) {
93
4.85M
    if (size_ < consumed_size + kHeaderSize)
94
12.5k
      break;  // Case A, not enough data to read even the header.
95
96
    // Read the header into |payload_size|.
97
4.84M
    uint32_t payload_size = 0;
98
4.84M
    const char* rd_ptr = buf() + consumed_size;
99
4.84M
    memcpy(base::AssumeLittleEndian(&payload_size), rd_ptr, kHeaderSize);
100
101
    // Saturate the |payload_size| to prevent overflows. The > capacity_ check
102
    // below will abort the parsing.
103
4.84M
    size_t next_frame_size =
104
4.84M
        std::min(static_cast<size_t>(payload_size), capacity_);
105
4.84M
    next_frame_size += kHeaderSize;
106
4.84M
    rd_ptr += kHeaderSize;
107
108
4.84M
    if (size_ < consumed_size + next_frame_size) {
109
      // Case B. We got the header but not the whole frame.
110
2.28k
      if (next_frame_size > capacity_) {
111
        // The caller is expected to shut down the socket and give up at this
112
        // point. If it doesn't do that and insists going on at some point it
113
        // will hit the capacity check in BeginReceive().
114
174
        PERFETTO_LOG("IPC Frame too large (size %zu)", next_frame_size);
115
174
        return false;
116
174
      }
117
2.11k
      break;
118
2.28k
    }
119
120
    // Case C. We got at least one header and whole frame.
121
4.84M
    DecodeFrame(rd_ptr, payload_size);
122
4.84M
    consumed_size += next_frame_size;
123
4.84M
  }
124
125
14.8k
  PERFETTO_DCHECK(consumed_size <= size_);
126
14.6k
  if (consumed_size > 0) {
127
    // Shift out the consumed data from the buffer. In the typical case (C)
128
    // there is nothing to shift really, just setting size_ = 0 is enough.
129
    // Shifting is only for the (unlikely) case D.
130
8.63k
    size_ -= consumed_size;
131
8.63k
    if (size_ > 0) {
132
      // Case D. We consumed some frames but there is a leftover at the end of
133
      // the buffer. Shift out the consumed bytes, so that on the next round
134
      // |buf_| starts with the header of the next unconsumed frame.
135
1.66k
      const char* move_begin = buf() + consumed_size;
136
1.66k
      PERFETTO_CHECK(move_begin > buf());
137
1.66k
      PERFETTO_CHECK(move_begin + size_ <= buf() + capacity_);
138
1.66k
      memmove(buf(), move_begin, size_);
139
1.66k
    }
140
    // If we just finished decoding a large frame that used more than one page,
141
    // release the extra memory in the buffer. Large frames should be quite
142
    // rare.
143
8.63k
    if (consumed_size > page_size) {
144
1.25k
      size_t size_rounded_up = (size_ / page_size + 1) * page_size;
145
1.25k
      if (size_rounded_up < capacity_) {
146
1.25k
        char* madvise_begin = buf() + size_rounded_up;
147
1.25k
        const size_t madvise_size = capacity_ - size_rounded_up;
148
1.25k
        PERFETTO_CHECK(madvise_begin > buf() + size_);
149
1.25k
        PERFETTO_CHECK(madvise_begin + madvise_size <= buf() + capacity_);
150
1.25k
        buf_.AdviseDontNeed(madvise_begin, madvise_size);
151
1.25k
      }
152
1.25k
    }
153
8.63k
  }
154
  // At this point |size_| == 0 for case C, > 0 for cases A, B, D.
155
14.6k
  return true;
156
14.6k
}
157
158
1.10M
std::unique_ptr<Frame> BufferedFrameDeserializer::PopNextFrame() {
159
1.10M
  if (decoded_frames_.empty())
160
5.79k
    return nullptr;
161
1.09M
  std::unique_ptr<Frame> frame = std::move(decoded_frames_.front());
162
1.09M
  decoded_frames_.pop_front();
163
1.09M
  return frame;
164
1.10M
}
165
166
4.84M
void BufferedFrameDeserializer::DecodeFrame(const char* data, size_t size) {
167
4.84M
  if (size == 0)
168
3.44M
    return;
169
1.40M
  std::unique_ptr<Frame> frame(new Frame);
170
1.40M
  if (frame->ParseFromArray(data, size))
171
1.34M
    decoded_frames_.push_back(std::move(frame));
172
1.40M
}
173
174
// static
175
197k
std::string BufferedFrameDeserializer::Serialize(const Frame& frame) {
176
197k
  std::vector<uint8_t> payload = frame.SerializeAsArray();
177
197k
  const uint32_t payload_size = static_cast<uint32_t>(payload.size());
178
197k
  std::string buf;
179
197k
  buf.resize(kHeaderSize + payload_size);
180
197k
  memcpy(&buf[0], base::AssumeLittleEndian(&payload_size), kHeaderSize);
181
197k
  memcpy(&buf[kHeaderSize], payload.data(), payload.size());
182
197k
  return buf;
183
197k
}
184
185
}  // namespace ipc
186
}  // namespace perfetto