20#include <choc/text/choc_JSON.h>
29#include <system_error>
70 std::vector<SwapPackFile>
files;
103 if (hex.size() % 2 != 0)
return std::nullopt;
104 auto nibble = [](
char c) ->
int {
105 if (c >=
'0' && c <=
'9')
return c -
'0';
106 if (c >=
'a' && c <=
'f')
return c -
'a' + 10;
107 if (c >=
'A' && c <=
'F')
return c -
'A' + 10;
110 std::vector<std::uint8_t> out;
111 out.reserve(hex.size() / 2);
112 for (std::size_t i = 0; i < hex.size(); i += 2) {
113 const int hi = nibble(hex[i]), lo = nibble(hex[i + 1]);
114 if (hi < 0 || lo < 0)
return std::nullopt;
115 out.push_back(
static_cast<std::uint8_t
>((hi << 4) | lo));
125 std::string&
error) {
128 auto v = choc::json::parse(std::string(json));
129 if (!v.isObject()) {
error =
"manifest is not a JSON object";
return std::nullopt; }
130 if (!v.hasObjectMember(
"id") || !v[
"id"].isString()) {
131 error =
"missing/invalid string 'id'";
return std::nullopt;
133 if (!v.hasObjectMember(
"plugin_id") || !v[
"plugin_id"].isString()) {
134 error =
"missing/invalid string 'plugin_id'";
return std::nullopt;
136 m.id = v[
"id"].getString();
137 m.plugin_id = v[
"plugin_id"].getString();
138 if (v.hasObjectMember(
"format_version") && v[
"format_version"].isInt())
139 m.format_version =
static_cast<int>(v[
"format_version"].getInt64());
140 if (!v.hasObjectMember(
"files") || !v[
"files"].isArray()) {
141 error =
"missing/invalid array 'files'";
return std::nullopt;
143 const auto& files = v[
"files"];
144 for (uint32_t i = 0; i < files.size(); ++i) {
145 const auto& f = files[i];
146 if (!f.isObject() || !f.hasObjectMember(
"path") || !f[
"path"].isString() ||
147 !f.hasObjectMember(
"sha256") || !f[
"sha256"].isString()) {
148 error =
"files[" + std::to_string(i) +
"] missing string 'path'/'sha256'";
152 pf.
path = f[
"path"].getString();
154 pf.
kind = f.hasObjectMember(
"kind") && f[
"kind"].isString()
157 m.files.push_back(std::move(pf));
161 if (v.hasObjectMember(
"pack_version") && v[
"pack_version"].isInt())
162 m.pack_version =
static_cast<std::uint64_t
>(v[
"pack_version"].getInt64());
163 if (v.hasObjectMember(
"pack_type") && v[
"pack_type"].isString())
165 if (v.hasObjectMember(
"update_channel") && v[
"update_channel"].isString())
166 m.update_channel = v[
"update_channel"].getString();
167 if (v.hasObjectMember(
"min_host_version") && v[
"min_host_version"].isInt())
168 m.min_host_version =
static_cast<int>(v[
"min_host_version"].getInt64());
169 if (v.hasObjectMember(
"capabilities") && v[
"capabilities"].isArray()) {
170 const auto& caps = v[
"capabilities"];
171 for (uint32_t i = 0; i < caps.size(); ++i)
172 if (caps[i].isString()) m.declared_capabilities.emplace_back(caps[i].getString());
177 if (v.hasObjectMember(
"signer") && v[
"signer"].isString()) {
178 if (
auto k =
swap_pack_hex_decode(v[
"signer"].getString())) m.signer_public_key = std::move(*k);
180 if (v.hasObjectMember(
"signature") && v[
"signature"].isString()) {
183 }
catch (
const std::exception& e) {
184 error = std::string(
"manifest JSON parse failed: ") + e.what();
195 auto to_lower = [](std::string s) {
196 for (
auto& c : s) c =
static_cast<char>(std::tolower(
static_cast<unsigned char>(c)));
199 for (
const auto& f : manifest.
files) {
200 const auto full = root / f.path;
202 if (!std::filesystem::exists(full, ec) || ec) {
205 std::ifstream in(full, std::ios::binary);
207 std::vector<std::uint8_t> bytes((std::istreambuf_iterator<char>(in)),
208 std::istreambuf_iterator<char>());
211 if (to_lower(actual) != to_lower(f.sha256_hex)) {
229 std::vector<std::uint8_t> out;
230 auto put_u32 = [&out](std::uint32_t n) {
231 out.push_back(
static_cast<std::uint8_t
>(n & 0xFF));
232 out.push_back(
static_cast<std::uint8_t
>((n >> 8) & 0xFF));
233 out.push_back(
static_cast<std::uint8_t
>((n >> 16) & 0xFF));
234 out.push_back(
static_cast<std::uint8_t
>((n >> 24) & 0xFF));
236 auto put_field = [&](std::string_view s) {
237 put_u32(
static_cast<std::uint32_t
>(s.size()));
238 out.insert(out.end(), s.begin(), s.end());
240 auto put_u64 = [&out](std::uint64_t n) {
241 for (
int i = 0; i < 8; ++i) out.push_back(
static_cast<std::uint8_t
>((n >> (8 * i)) & 0xFF));
246 put_field(
"pulp-swap-pack-v2");
248 put_field(m.plugin_id);
249 put_u32(
static_cast<std::uint32_t
>(m.format_version));
250 put_u64(m.pack_version);
252 put_field(m.update_channel);
253 put_u32(
static_cast<std::uint32_t
>(m.min_host_version));
254 put_u32(
static_cast<std::uint32_t
>(m.declared_capabilities.size()));
255 for (
const auto& c : m.declared_capabilities) put_field(c);
256 put_u32(
static_cast<std::uint32_t
>(m.files.size()));
257 for (
const auto& f : m.files) {
259 put_field(f.sha256_hex);
271 const std::vector<std::uint8_t>& trusted_public_key) {
287 message.data(), message.size())) {
302 const std::vector<std::uint8_t>& trusted_public_key) {
304 if (!sig.ok())
return sig;
316 out.reserve(s.size() + 2);
319 case '"': out +=
"\\\"";
break;
320 case '\\': out +=
"\\\\";
break;
321 case '\n': out +=
"\\n";
break;
322 case '\r': out +=
"\\r";
break;
323 case '\t': out +=
"\\t";
break;
325 if (
static_cast<unsigned char>(c) < 0x20) {
326 static const char* d =
"0123456789abcdef";
328 out.push_back(d[(c >> 4) & 0xF]);
329 out.push_back(d[c & 0xF]);
343 std::string j =
"{\n";
344 j +=
" \"id\": " + q(m.id) +
",\n";
345 j +=
" \"plugin_id\": " + q(m.plugin_id) +
",\n";
346 j +=
" \"format_version\": " + std::to_string(m.format_version) +
",\n";
347 j +=
" \"pack_version\": " + std::to_string(m.pack_version) +
",\n";
349 j +=
" \"update_channel\": " + q(m.update_channel) +
",\n";
350 j +=
" \"min_host_version\": " + std::to_string(m.min_host_version) +
",\n";
351 j +=
" \"capabilities\": [";
352 for (std::size_t i = 0; i < m.declared_capabilities.size(); ++i)
353 j += (i ?
", " :
"") + q(m.declared_capabilities[i]);
355 j +=
" \"files\": [\n";
356 for (std::size_t i = 0; i < m.files.size(); ++i) {
357 const auto& f = m.files[i];
358 j +=
" {\"path\": " + q(f.path) +
", \"sha256\": " + q(f.sha256_hex) +
360 j += (i + 1 < m.files.size()) ?
",\n" :
"\n";
363 if (!m.signer_public_key.empty())
365 if (!m.signature.empty())
std::string sha256_hex(const uint8_t *data, size_t size)
Compute SHA-256 hash as hex string (64 chars).
std::string hex_encode(const uint8_t *data, size_t size)
Lowercase-hex encode raw bytes.
Definition crypto.hpp:32
bool ed25519_verify(const uint8_t *public_key_32, size_t public_key_size, const uint8_t *signature_64, size_t signature_size, const uint8_t *message, size_t message_size)
Verify a 64-byte detached signature against a message + 32-byte public key.
constexpr size_t ed25519_public_key_size
Ed25519 key sizes (RFC 8032 ยง5.1).
Definition crypto.hpp:159
constexpr size_t ed25519_signature_size
Definition crypto.hpp:162