summaryrefslogtreecommitdiff
path: root/command_processor.cpp
blob: ea223af1b7acc220c16918950882b8dca9cd1b52 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
/*
 * Copyright (C) 2016 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 <string.h>

#include <algorithm>
#include <cinttypes>
#include <string>
#include <tuple>
#include <utility>

#include "android-base/logging.h"
#include "android-base/stringprintf.h"

#include "wifilogd/byte_buffer.h"
#include "wifilogd/command_processor.h"
#include "wifilogd/local_utils.h"
#include "wifilogd/memory_reader.h"
#include "wifilogd/protocol.h"

namespace android {
namespace wifilogd {

using ::android::base::unique_fd;

using local_utils::CopyFromBufferOrDie;
using local_utils::GetMaxVal;

namespace {

uint32_t NsecToUsec(uint32_t nsec);

class TimestampHeader {
 public:
  TimestampHeader& set_since_boot_awake_only(Os::Timestamp new_value) {
    since_boot_awake_only = new_value;
    return *this;
  }

  TimestampHeader& set_since_boot_with_sleep(Os::Timestamp new_value) {
    since_boot_with_sleep = new_value;
    return *this;
  }

  TimestampHeader& set_since_epoch(Os::Timestamp new_value) {
    since_epoch = new_value;
    return *this;
  }

  // Returns a string with a formatted representation of the timestamps
  // contained within this header.
  std::string ToString() const {
    const auto& awake_time = since_boot_awake_only;
    const auto& up_time = since_boot_with_sleep;
    const auto& wall_time = since_epoch;
    return base::StringPrintf("%" PRIu32 ".%06" PRIu32
                              " "
                              "%" PRIu32 ".%06" PRIu32
                              " "
                              "%" PRIu32 ".%06" PRIu32,
                              awake_time.secs, NsecToUsec(awake_time.nsecs),
                              up_time.secs, NsecToUsec(up_time.nsecs),
                              wall_time.secs, NsecToUsec(wall_time.nsecs));
  }

  Os::Timestamp since_boot_awake_only;
  Os::Timestamp since_boot_with_sleep;
  Os::Timestamp since_epoch;  // non-monotonic
};

constexpr char kUnprintableCharReplacement = '?';

std::string MakeSanitizedString(const uint8_t* buf, size_t buf_len);

std::string GetStringFromMemoryReader(NONNULL MemoryReader* buffer_reader,
                                      const size_t desired_len) {
  constexpr char kBufferOverrunError[] = "[buffer-overrun]";
  constexpr char kZeroLengthError[] = "[empty]";
  if (!desired_len) {
    // TODO(b/32098735): Increment stats counter.
    return kZeroLengthError;
  }

  auto effective_len = desired_len;
  if (buffer_reader->size() < effective_len) {
    // TODO(b/32098735): Increment stats counter.
    effective_len = buffer_reader->size();
  }

  auto out = MakeSanitizedString(buffer_reader->GetBytesOrDie(effective_len),
                                 effective_len);
  if (effective_len < desired_len) {
    out += kBufferOverrunError;
  }

  return out;
}

std::string MakeSanitizedString(const uint8_t* buf, size_t buf_len) {
  std::string out(buf_len, '\0');
  std::replace_copy_if(buf, buf + buf_len, &out.front(),
                       [](auto c) { return !local_utils::IsAsciiPrintable(c); },
                       kUnprintableCharReplacement);
  return out;
}

uint32_t NsecToUsec(uint32_t nsec) { return nsec / 1000; }

}  // namespace

CommandProcessor::CommandProcessor(size_t buffer_size_bytes)
    : current_log_buffer_(buffer_size_bytes), os_(new Os()) {}

CommandProcessor::CommandProcessor(size_t buffer_size_bytes,
                                   std::unique_ptr<Os> os)
    : current_log_buffer_(buffer_size_bytes), os_(std::move(os)) {}

CommandProcessor::~CommandProcessor() {}

bool CommandProcessor::ProcessCommand(const void* input_buffer,
                                      size_t n_bytes_read, int fd) {
  unique_fd wrapped_fd(fd);

  if (n_bytes_read < sizeof(protocol::Command)) {
    // TODO(b/32098735): Increment stats counter.
    return false;
  }

  const auto& command_header =
      CopyFromBufferOrDie<protocol::Command>(input_buffer, n_bytes_read);
  switch (command_header.opcode) {
    using protocol::Opcode;
    case Opcode::kWriteAsciiMessage:
      // Copy the entire command to the log. This defers the cost of
      // validating the rest of the CommandHeader until we dump the
      // message.
      //
      // Note that most messages will be written but never read. So, in
      // the common case, the validation cost is actually eliminated,
      // rather than just deferred.
      return CopyCommandToLog(input_buffer, n_bytes_read);
    case Opcode::kDumpBuffers:
      return Dump(std::move(wrapped_fd));
  }

  LOG(DEBUG) << "Received unexpected opcode "
             << local_utils::CastEnumToInteger(command_header.opcode);
  // TODO(b/32098735): Increment stats counter.
  return false;
}

// Private methods below.

bool CommandProcessor::CopyCommandToLog(const void* command_buffer,
                                        size_t command_len_in) {
  const uint16_t command_len =
      SAFELY_CLAMP(command_len_in, uint16_t, 0, protocol::kMaxMessageSize);

  uint16_t total_size;
  static_assert(GetMaxVal(total_size) > sizeof(TimestampHeader) &&
                    GetMaxVal(total_size) - sizeof(TimestampHeader) >=
                        protocol::kMaxMessageSize,
                "total_size cannot represent some input messages");
  total_size = sizeof(TimestampHeader) + command_len;
  CHECK(current_log_buffer_.CanFitEver(total_size));

  if (!current_log_buffer_.CanFitNow(total_size)) {
    // TODO(b/31867689):
    // 1. compress current buffer
    // 2. move old buffer to linked list
    // 3. prune old buffers, if needed
    current_log_buffer_.Clear();
  }
  CHECK(current_log_buffer_.CanFitNow(total_size));

  const auto tstamp_header =
      TimestampHeader()
          .set_since_boot_awake_only(os_->GetTimestamp(CLOCK_MONOTONIC))
          .set_since_boot_with_sleep(os_->GetTimestamp(CLOCK_BOOTTIME))
          .set_since_epoch(os_->GetTimestamp(CLOCK_REALTIME));
  const auto message_buf =
      ByteBuffer<sizeof(TimestampHeader) + protocol::kMaxMessageSize>()
          .AppendOrDie(&tstamp_header, sizeof(tstamp_header))
          .AppendOrDie(command_buffer, command_len);
  bool did_write = current_log_buffer_.Append(
      message_buf.data(),
      SAFELY_CLAMP(message_buf.size(), uint16_t, 0, GetMaxVal<uint16_t>()));
  if (!did_write) {
    // Given that we checked for enough free space, Append() should
    // have succeeded. Hence, a failure here indicates a logic error,
    // rather than a runtime error.
    LOG(FATAL) << "Unexpected failure to Append()";
  }

  return true;
}

bool CommandProcessor::Dump(unique_fd dump_fd) {
  MessageBuffer::ScopedRewinder rewinder(&current_log_buffer_);
  while (auto buffer_reader =
             MemoryReader(current_log_buffer_.ConsumeNextMessage())) {
    const auto& tstamp_header = buffer_reader.CopyOutOrDie<TimestampHeader>();
    const auto& command_header =
        buffer_reader.CopyOutOrDie<protocol::Command>();

    // TOOO(b/32256098): validate |buffer_reader.size()| against payload_len,
    // and use a smaller size if necessary. Update a stats counter if
    // payload_len and
    // buflen do not match.

    std::string output_string = tstamp_header.ToString();
    switch (command_header.opcode) {
      using protocol::Opcode;
      case Opcode::kWriteAsciiMessage:
        output_string += ' ' + FormatAsciiMessage(buffer_reader);
        break;
      case Opcode::kDumpBuffers:
        LOG(FATAL) << "Unexpected DumpBuffers command in log";
        break;
    }
    output_string += '\n';

    size_t n_written;
    Os::Errno err;
    std::tie(n_written, err) =
        os_->Write(dump_fd, output_string.data(), output_string.size());
    if (err == EINTR) {
      // The next write will probably succeed. We dont't retry the current
      // message, however, because we want to guarantee forward progress.
      //
      // TODO(b/32098735): Increment a counter, and attempt to output that
      // counter after we've dumped all the log messages.
    } else if (err) {
      // Any error other than EINTR is considered unrecoverable.
      LOG(ERROR) << "Terminating log dump, due to " << strerror(err);
      return false;
    }
  }

  return true;
}

std::string CommandProcessor::FormatAsciiMessage(MemoryReader buffer_reader) {
  constexpr char kShortHeaderError[] = "[truncated-header]";
  if (buffer_reader.size() < sizeof(protocol::AsciiMessage)) {
    // TODO(b/32098735): Increment stats counter.
    return kShortHeaderError;
  }

  const auto& ascii_message_header =
      buffer_reader.CopyOutOrDie<protocol::AsciiMessage>();
  const std::string& formatted_tag =
      GetStringFromMemoryReader(&buffer_reader, ascii_message_header.tag_len);
  const std::string& formatted_msg =
      GetStringFromMemoryReader(&buffer_reader, ascii_message_header.data_len);
  return formatted_tag + ' ' + formatted_msg;
}

}  // namespace wifilogd
}  // namespace android