current commit
250914.10 · inspector: ensure adequate memory allocation for `Binary::toBase64`
#include "node_string.h"
#include "crdtp/json.h"
#include "node/inspector/protocol/Protocol.h"
#include "simdutf.h"
#include "util-inl.h"

namespace crdtp {

bool ProtocolTypeTraits<std::string>::Deserialize(DeserializerState* state,
                                                  std::string* value) {
  if (state->tokenizer()->TokenTag() == cbor::CBORTokenTag::STRING8) {
    span<uint8_t> cbor_span = state->tokenizer()->GetString8();
    value->assign(reinterpret_cast<const char*>(cbor_span.data()),
                  cbor_span.size());
    return true;
  }
  CHECK(state->tokenizer()->TokenTag() == cbor::CBORTokenTag::STRING16);
  span<uint8_t> utf16le = state->tokenizer()->GetString16WireRep();

  value->assign(node::inspector::protocol::StringUtil::fromUTF16LE(
      reinterpret_cast<const uint16_t*>(utf16le.data()),
      utf16le.size() / sizeof(uint16_t)));
  return true;
}

void ProtocolTypeTraits<std::string>::Serialize(const std::string& value,
                                                std::vector<uint8_t>* bytes) {
  cbor::EncodeString8(SpanFrom(value), bytes);
}

bool ProtocolTypeTraits<node::inspector::protocol::Binary>::Deserialize(
    DeserializerState* state, node::inspector::protocol::Binary* value) {
  CHECK(state->tokenizer()->TokenTag() == cbor::CBORTokenTag::BINARY);
  span<uint8_t> cbor_span = state->tokenizer()->GetBinary();
  *value = node::inspector::protocol::Binary::fromSpan(cbor_span);
  return true;
}

void ProtocolTypeTraits<node::inspector::protocol::Binary>::Serialize(
    const node::inspector::protocol::Binary& value,
    std::vector<uint8_t>* bytes) {
  cbor::EncodeString8(SpanFrom(value.toBase64()), bytes);
}

}  // namespace crdtp

namespace node {
namespace inspector {
namespace protocol {

String StringUtil::StringViewToUtf8(v8_inspector::StringView view) {
  if (view.length() == 0)
    return "";
  if (view.is8Bit()) {
    return std::string(reinterpret_cast<const char*>(view.characters8()),
                       view.length());
  }
  return fromUTF16(view.characters16(), view.length());
}

String StringUtil::fromUTF16(const uint16_t* data, size_t length) {
  auto casted_data = reinterpret_cast<const char16_t*>(data);
  size_t expected_utf8_length =
      simdutf::utf8_length_from_utf16(casted_data, length);
  MaybeStackBuffer<char> buffer(expected_utf8_length);
  // simdutf::convert_utf16_to_utf8 returns zero in case of error.
  size_t utf8_length =
      simdutf::convert_utf16_to_utf8(casted_data, length, buffer.out());
  // We have that utf8_length == expected_utf8_length if and only
  // if the input was a valid UTF-16 string. Otherwise, utf8_length
  // must be zero.
  CHECK(utf8_length == 0 || utf8_length == expected_utf8_length);
  // An invalid UTF-16 input will generate the empty string:
  return String(buffer.out(), utf8_length);
}

String StringUtil::fromUTF8(const uint8_t* data, size_t length) {
  return std::string(reinterpret_cast<const char*>(data), length);
}

String StringUtil::fromUTF16LE(const uint16_t* data, size_t length) {
  auto casted_data = reinterpret_cast<const char16_t*>(data);
  size_t expected_utf8_length =
      simdutf::utf8_length_from_utf16le(casted_data, length);
  MaybeStackBuffer<char> buffer(expected_utf8_length);
  // simdutf::convert_utf16le_to_utf8 returns zero in case of error.
  size_t utf8_length =
      simdutf::convert_utf16le_to_utf8(casted_data, length, buffer.out());
  // We have that utf8_length == expected_utf8_length if and only
  // if the input was a valid UTF-16 string. Otherwise, utf8_length
  // must be zero.
  CHECK(utf8_length == 0 || utf8_length == expected_utf8_length);
  // An invalid UTF-16 input will generate the empty string:
  return String(buffer.out(), utf8_length);
}

const uint8_t* StringUtil::CharactersUTF8(const std::string_view s) {
  return reinterpret_cast<const uint8_t*>(s.data());
}

size_t StringUtil::CharacterCount(const std::string_view s) {
  // Return the length of underlying representation storage.
  // E.g. for std::basic_string_view<char>, return its byte length.
  // If we adopt a variant underlying store string type, like
  // `v8_inspector::StringView`, for UTF16, return the length of the
  // underlying uint16_t store.
  return s.length();
}

String Binary::toBase64() const {
  size_t expected_base64_length =
      simdutf::base64_length_from_binary(bytes_->size());
  MaybeStackBuffer<char> buffer(expected_base64_length);

  size_t len =
      simdutf::binary_to_base64(reinterpret_cast<const char*>(bytes_->data()),
                                bytes_->size(),
                                buffer.out());
  CHECK_EQ(len, expected_base64_length);
  return buffer.ToString();
}

// static
Binary Binary::concat(const std::vector<Binary>& binaries) {
  size_t total_size = 0;
  for (const auto& binary : binaries) {
    total_size += binary.size();
  }
  auto bytes = std::make_shared<std::vector<uint8_t>>(total_size);
  uint8_t* data_ptr = bytes->data();
  for (const auto& binary : binaries) {
    memcpy(data_ptr, binary.data(), binary.size());
    data_ptr += binary.size();
  }
  return Binary(bytes);
}

// static
Binary Binary::fromBase64(const String& base64, bool* success) {
  Binary binary{};
  size_t base64_len = simdutf::maximal_binary_length_from_base64(
      base64.data(), base64.length());
  binary.bytes_->resize(base64_len);

  simdutf::result result;
  result =
      simdutf::base64_to_binary(base64.data(),
                                base64.length(),
                                reinterpret_cast<char*>(binary.bytes_->data()));
  CHECK_EQ(result.error, simdutf::error_code::SUCCESS);
  return binary;
}

// static
Binary Binary::fromUint8Array(v8::Local<v8::Uint8Array> data) {
  auto bytes = std::make_shared<std::vector<uint8_t>>(data->ByteLength());
  size_t size = data->CopyContents(bytes->data(), data->ByteLength());
  CHECK_EQ(size, data->ByteLength());
  return Binary(bytes);
}

}  // namespace protocol
}  // namespace inspector
}  // namespace node