blob: b3433ac21d87a40800db38bb22b2a95f6e4d7405 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "lib/fastboot/fastboot_base.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <span>
#include <string>
namespace fastboot {
bool FastbootBase::MatchCommand(std::string_view cmd, std::string_view ref) {
if (cmd.compare(0, strlen(kOemPrefix), kOemPrefix) == 0) {
// For oem commands, we require that arguments are separated by spaces. The first argument after
// oem specifies the command type. `ref` should look like "oem <command name>".
return cmd.compare(0, cmd.find(" ", sizeof(kOemPrefix)), ref, 0, ref.size()) == 0;
} else {
// find the first occurrence of ":". if there isn't, return value will be
// string::npos, which will lead to full string comparison.
size_t pos = cmd.find(":");
return cmd.compare(0, pos, ref, 0, ref.size()) == 0;
}
}
zx::result<> FastbootBase::SendResponse(ResponseType resp_type, std::string_view message,
Transport* transport, zx::result<> status_code) {
const char* type = nullptr;
bool multi_message_split = false;
if (resp_type == ResponseType::kOkay) {
type = "OKAY";
} else if (resp_type == ResponseType::kInfo) {
type = "INFO";
multi_message_split = true;
} else if (resp_type == ResponseType::kFail) {
type = "FAIL";
} else {
return zx::error(ZX_ERR_INVALID_ARGS);
}
char resp_buffer[kMaxCommandPacketSize + 1] = {};
std::span<char> buf(resp_buffer, kMaxCommandPacketSize);
// Print type
size_t s = snprintf(buf.data(), buf.size(), "%s", type);
if (s >= buf.size()) {
return zx::error(ZX_ERR_BUFFER_TOO_SMALL);
}
buf = buf.subspan(s);
// Print message to the buffer and send it to host.
// If `multi_message_split` is set, message is not truncated. It is split and send back in
// multiple messages.
std::string_view msg_left = message;
do {
auto line_end = multi_message_split ? msg_left.find('\n') : std::string_view::npos;
auto line_left = msg_left.substr(0, line_end);
msg_left.remove_prefix(std::min(line_left.size() + 1, msg_left.size()));
do {
auto print_len = std::min(buf.size(), line_left.size());
snprintf(buf.data(), print_len + 1, "%s", line_left.data());
line_left.remove_prefix(print_len);
// If error status set then append it to the last message
// Can be truncated due to buffer length limitation.
if (msg_left.empty() && line_left.empty() && status_code.is_error()) {
buf = buf.subspan(print_len);
#if defined(__Fuchsia__)
snprintf(buf.data(), buf.size(), "(%s)", status_code.status_string());
#else
snprintf(buf.data(), buf.size(), "(%d)", status_code.error_value());
#endif
}
if (zx::result<> ret = transport->Send(std::string_view(resp_buffer)); ret.is_error()) {
return zx::error(ret.status_value());
}
} while (multi_message_split && !line_left.empty());
} while (multi_message_split && !msg_left.empty());
return status_code;
}
zx::result<> FastbootBase::SendDataResponse(size_t data_size, Transport* transport) {
char resp_buffer[kMaxCommandPacketSize + 1] = {0};
snprintf(resp_buffer, sizeof(resp_buffer), "DATA%08zx", data_size);
return transport->Send(std::string_view{resp_buffer, strlen(resp_buffer)});
}
zx::result<> FastbootBase::ProcessPacket(Transport* transport) {
if (!transport->PeekPacketSize()) {
return zx::ok();
}
if (state_ == State::kCommand) {
char command[kMaxCommandPacketSize + 1] = {0};
zx::result<size_t> ret = transport->ReceivePacket(command, sizeof(command));
if (!ret.is_ok()) {
return SendResponse(ResponseType::kFail, "Fail to read command", transport,
zx::error(ret.status_value()));
}
if (MatchCommand(command, "download")) {
return Download(command, transport);
}
return ProcessCommand(command, transport);
} else if (state_ == State::kDownload) {
size_t packet_size = transport->PeekPacketSize();
if (packet_size > remaining_download_size_) {
ClearDownload();
return SendResponse(ResponseType::kFail, "Unexpected amount of download", transport);
}
size_t offset = total_download_size() - remaining_download_size();
uint8_t* start = reinterpret_cast<uint8_t*>(download_buffer_) + offset;
if (zx::result<size_t> ret = transport->ReceivePacket(start, transport->PeekPacketSize());
ret.is_error()) {
ClearDownload();
return SendResponse(ResponseType::kFail, "Failed to receive download packet", transport,
zx::error(ret.status_value()));
}
remaining_download_size_ -= packet_size;
if (remaining_download_size_ == 0) {
state_ = State::kCommand;
return SendResponse(ResponseType::kOkay, "", transport);
}
return zx::ok();
}
return zx::ok();
}
void FastbootBase::ExtractCommandArgs(std::string_view cmd, const char* delimeter,
CommandArgs& ret) {
ret.num_args = 0;
size_t start = 0;
auto find_pos = cmd.find(delimeter, start);
while (find_pos != std::string_view::npos && ret.num_args < kMaxCommandArgs) {
// Skips empty argument.
if (start < find_pos) {
ret.args[ret.num_args++] = cmd.substr(start, find_pos - start);
}
start = find_pos + 1;
find_pos = cmd.find(delimeter, start);
}
if (start < cmd.size()) {
ret.args[ret.num_args++] = cmd.substr(start);
}
}
zx::result<> FastbootBase::Download(std::string_view cmd, Transport* transport) {
ClearDownload();
CommandArgs args;
ExtractCommandArgs(cmd, ":", args);
if (args.num_args < 2) {
return SendResponse(ResponseType::kFail, "Not enough argument", transport);
}
total_download_size_ = static_cast<size_t>(strtoul(args.args[1].data(), nullptr, 16));
if (total_download_size_ == 0) {
return SendResponse(ResponseType::kFail, "Empty size download is not allowed", transport);
}
zx::result<void*> buffer = GetDownloadBuffer(total_download_size_);
if (buffer.is_error()) {
ClearDownload();
return SendResponse(ResponseType::kFail, "Failed to prepare download", transport,
zx::error(buffer.status_value()));
}
download_buffer_ = buffer.value();
remaining_download_size_ = total_download_size_;
state_ = State::kDownload;
return SendDataResponse(total_download_size_, transport);
}
void FastbootBase::ClearDownload() {
total_download_size_ = 0;
remaining_download_size_ = 0;
state_ = State::kCommand;
download_buffer_ = nullptr;
DoClearDownload();
}
} // namespace fastboot