blob: 3906ae9a2e1db634d133dd050c9f02c6f1ecb7b6 [file]
// Copyright 2026 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <lib/boot-shim-utils/commandline-bootloader-file-item.h>
#include <stdio.h>
#include <string.h>
#include <limits>
#include <ranges>
#include "third_party/modp_b64/modp_b64.h"
namespace {
// Executes `callback` on each `cmdline` arg starting with `prefix`.
template <typename Callback>
void ForEachChunk(std::string_view cmdline, std::string_view prefix, Callback&& callback) {
// Don't bother with quoting or complex cases, we don't expect the bootloader to be passing
// anything that won't work with simple space delimiters.
for (const auto range : std::views::split(cmdline, std::string_view(" "))) {
std::string_view arg(range.begin(), range.end());
if (arg.starts_with(prefix)) {
callback(arg.substr(prefix.length()));
}
}
}
// Returns the `ZBI_TYPE_BOOTLOADER_FILE` payload size, or error if it won't fit.
fit::result<CommandlineBootloaderFileItem::DataZbi::Error, uint32_t> PayloadSize(
std::string_view filename, size_t content_size) {
// Filename length must fit in a single byte.
if (filename.size() > std::numeric_limits<uint8_t>::max()) {
return fit::error(CommandlineBootloaderFileItem::DataZbi::Error{
.zbi_error = "Bootloader file name overflow",
.item_offset = 0,
});
}
// ZBI item payload length must fit in a U32.
size_t payload_size = 1 + filename.size() + content_size;
if (payload_size <= content_size || payload_size > std::numeric_limits<uint32_t>::max()) {
return fit::error(CommandlineBootloaderFileItem::DataZbi::Error{
.zbi_error = "Bootloader file size overflow",
.item_offset = 0,
});
}
return fit::ok(static_cast<uint32_t>(payload_size));
}
} // namespace
void CommandlineBootloaderFileItem::Init(std::string_view cmdline, std::string_view prefix,
std::string_view filename) {
cmdline_ = cmdline;
prefix_ = prefix;
filename_ = filename;
// Pre-calculate Base64 length since we'll need this a few times.
base64_size_ = 0;
ForEachChunk(cmdline_, prefix_, [&](std::string_view chunk) { base64_size_ += chunk.size(); });
}
size_t CommandlineBootloaderFileItem::size_bytes() const {
if (base64_size_ == 0) {
return 0;
}
// This function can't fail so just return 0 (no ZBI space allocation) on error; we'll report the
// actual error later in `AppendItems()`.
auto res = PayloadSize(filename_, base64_size_);
return res.is_ok() ? ItemSize(res.value()) : 0;
}
fit::result<CommandlineBootloaderFileItem::DataZbi::Error>
CommandlineBootloaderFileItem::AppendItems(DataZbi& zbi) const {
if (base64_size_ == 0) {
return fit::ok();
}
auto base64_payload_size = PayloadSize(filename_, base64_size_);
if (base64_payload_size.is_error()) {
return base64_payload_size.take_error();
}
auto zbi_item = zbi.Append({
.type = ZBI_TYPE_BOOTLOADER_FILE,
// Request enough buffer for the filename header plus full Base64 capacity; we will shrink
// the final item length after decoding the data.
.length = *base64_payload_size,
.extra = 0,
.flags = 0,
.magic = ZBI_ITEM_MAGIC,
});
if (zbi_item.is_error()) {
return zbi_item.take_error();
}
// Write the header: filename length (1 byte) and filename.
zbi_item->payload[0] = static_cast<std::byte>(filename_.size());
memcpy(zbi_item->payload.data() + 1, filename_.data(), filename_.size());
// Copy the Base64 data after the header.
char* base64_buffer = reinterpret_cast<char*>(zbi_item->payload.data()) + 1 + filename_.size();
size_t offset = 0;
ForEachChunk(cmdline_, prefix_, [&](std::string_view chunk) {
memcpy(base64_buffer + offset, chunk.data(), chunk.size());
offset += chunk.size();
});
// Decode Base64 in-place. Since decoded data is always smaller, this only overwrites data which
// has already been read so is safe to do.
size_t decoded_size = modp_b64_decode(base64_buffer, base64_buffer, base64_size_);
if (decoded_size == MODP_B64_ERROR) {
return fit::error(DataZbi::Error{
.zbi_error = "Invalid Base64 in commandline bootloader file chunks",
.item_offset = 0,
});
}
printf("commandline-bootloader-file-item: registering bootloader file '%.*s' (%zu bytes)\n",
static_cast<int>(filename_.size()), filename_.data(), decoded_size);
fit::result final_payload_size = PayloadSize(filename_, decoded_size);
if (final_payload_size.is_error()) {
return final_payload_size.take_error();
}
// Resize the item to only contain the decoded data size.
if (auto trim_res = zbi.TrimLastItem(*zbi_item, *final_payload_size); trim_res.is_error()) {
return trim_res.take_error();
}
return fit::ok();
}