kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

kpkg.c (21812B)


      1 #include "kpkg.h"
      2 
      3 #include <stdio.h>
      4 #include <stdlib.h>
      5 #include <string.h>
      6 
      7 #include "blake2b.h"
      8 
      9 #define DESC_LINE_MAX 1024u
     10 
     11 static void put_u32le(uint8_t* p, uint32_t v) {
     12   p[0] = (uint8_t)v;
     13   p[1] = (uint8_t)(v >> 8);
     14   p[2] = (uint8_t)(v >> 16);
     15   p[3] = (uint8_t)(v >> 24);
     16 }
     17 
     18 static void put_u64le(uint8_t* p, uint64_t v) {
     19   unsigned i;
     20   for (i = 0; i < 8u; ++i) p[i] = (uint8_t)(v >> (8u * i));
     21 }
     22 
     23 static uint32_t get_u32le(const uint8_t* p) {
     24   return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) |
     25          ((uint32_t)p[3] << 24);
     26 }
     27 
     28 static uint64_t get_u64le(const uint8_t* p) {
     29   uint64_t v = 0;
     30   unsigned i;
     31   for (i = 0; i < 8u; ++i) v |= ((uint64_t)p[i]) << (8u * i);
     32   return v;
     33 }
     34 
     35 static int emit(KitWriter* out, const char* s) {
     36   return kit_writer_write(out, s, strlen(s)) == KIT_OK ? DIST_OK : DIST_ERR;
     37 }
     38 
     39 static int emit_kv(KitWriter* out, const char* key, const char* val) {
     40   char line[DESC_LINE_MAX];
     41   snprintf(line, sizeof line, "%s = %s\n", key, val);
     42   return emit(out, line);
     43 }
     44 
     45 static int emit_u64(KitWriter* out, const char* key, uint64_t v) {
     46   char num[24];
     47   snprintf(num, sizeof num, "%llu", (unsigned long long)v);
     48   return emit_kv(out, key, num);
     49 }
     50 
     51 static int emit_hex(KitWriter* out, const char* key, const uint8_t* h) {
     52   char hex[2 * DIST_BLAKE2B_LEN + 1];
     53   dist_hex_encode(hex, h, DIST_BLAKE2B_LEN);
     54   return emit_kv(out, key, hex);
     55 }
     56 
     57 static char* trim_lead(char* s) {
     58   while (*s == ' ' || *s == '\t') ++s;
     59   return s;
     60 }
     61 
     62 static void trim_trail(char* s) {
     63   size_t n = strlen(s);
     64   while (n && (s[n - 1] == ' ' || s[n - 1] == '\t' || s[n - 1] == '\r' ||
     65                s[n - 1] == '\n'))
     66     s[--n] = '\0';
     67 }
     68 
     69 static int set_err(char* err, size_t cap, const char* msg) {
     70   if (err && cap) snprintf(err, cap, "%s", msg);
     71   return DIST_ERR;
     72 }
     73 
     74 static int parse_u64_strict(const char* s, uint64_t* out) {
     75   uint64_t v = 0;
     76   if (!*s) return DIST_ERR;
     77   while (*s) {
     78     uint64_t digit;
     79     if (*s < '0' || *s > '9') return DIST_ERR;
     80     digit = (uint64_t)(*s - '0');
     81     if (v > (UINT64_MAX - digit) / 10u) return DIST_ERR;
     82     v = v * 10u + digit;
     83     ++s;
     84   }
     85   *out = v;
     86   return DIST_OK;
     87 }
     88 
     89 static int parse_hex32(uint8_t out[DIST_BLAKE2B_LEN], const char* val) {
     90   if (strlen(val) != 2 * DIST_BLAKE2B_LEN) return DIST_ERR;
     91   return dist_hex_decode(out, val, DIST_BLAKE2B_LEN);
     92 }
     93 
     94 static int copy_field(char* out, size_t cap, const char* val) {
     95   size_t n = strlen(val);
     96   if (n >= cap) return DIST_ERR;
     97   memcpy(out, val, n + 1u);
     98   return DIST_OK;
     99 }
    100 
    101 static int seen_once(uint32_t* seen, uint32_t bit) {
    102   if (*seen & bit) return DIST_ERR;
    103   *seen |= bit;
    104   return DIST_OK;
    105 }
    106 
    107 void dist_kpkg3_region_root(uint8_t out[DIST_BLAKE2B_LEN], const char* kind,
    108                             const uint8_t* data, size_t len) {
    109   uint8_t region[DIST_BLAKE2B_LEN];
    110   DistBlake2b h;
    111   static const uint8_t dom[] = "kit region v1";
    112   dist_blake2b_init(&h, DIST_BLAKE2B_LEN);
    113   if (len) dist_blake2b_update(&h, data, len);
    114   dist_blake2b_final(&h, region);
    115 
    116   dist_blake2b_init(&h, DIST_BLAKE2B_LEN);
    117   dist_blake2b_update(&h, dom, sizeof dom - 1u);
    118   dist_blake2b_update(&h, (const uint8_t*)kind, strlen(kind));
    119   dist_blake2b_update(&h, region, DIST_BLAKE2B_LEN);
    120   dist_blake2b_final(&h, out);
    121 }
    122 
    123 int dist_kpkg3_write_header(KitWriter* out, const DistKpkg3Header* h) {
    124   uint8_t b[DIST_KPKG3_HEADER_SIZE];
    125   size_t off = 16u;
    126   memset(b, 0, sizeof b);
    127   memcpy(b, DIST_KPKG3_MAGIC, 7u);
    128   put_u32le(b + 8u, DIST_KPKG3_VERSION);
    129   put_u32le(b + 12u, DIST_KPKG3_HEADER_SIZE);
    130 #define PUT3(v)              \
    131   do {                       \
    132     put_u64le(b + off, (v)); \
    133     off += 8u;               \
    134   } while (0)
    135   PUT3(h->manifest_offset);
    136   PUT3(h->manifest_size);
    137   PUT3(h->signature_offset);
    138   PUT3(h->signature_size);
    139   PUT3(h->descriptor_offset);
    140   PUT3(h->descriptor_size);
    141   PUT3(h->descriptor_signature_offset);
    142   PUT3(h->descriptor_signature_size);
    143   PUT3(h->pubkey_offset);
    144   PUT3(h->pubkey_size);
    145 #undef PUT3
    146   return kit_writer_write(out, b, sizeof b) == KIT_OK ? DIST_OK : DIST_ERR;
    147 }
    148 
    149 int dist_kpkg3_read_header(const uint8_t* data, size_t len,
    150                            DistKpkg3Header* h) {
    151   size_t off = 16u;
    152   if (len < DIST_KPKG3_HEADER_SIZE) return DIST_ERR;
    153   if (memcmp(data, DIST_KPKG3_MAGIC, 7u) != 0) return DIST_ERR;
    154   if (get_u32le(data + 8u) != DIST_KPKG3_VERSION ||
    155       get_u32le(data + 12u) != DIST_KPKG3_HEADER_SIZE)
    156     return DIST_ERR;
    157 #define GET3(dst)                  \
    158   do {                             \
    159     (dst) = get_u64le(data + off); \
    160     off += 8u;                     \
    161   } while (0)
    162   GET3(h->manifest_offset);
    163   GET3(h->manifest_size);
    164   GET3(h->signature_offset);
    165   GET3(h->signature_size);
    166   GET3(h->descriptor_offset);
    167   GET3(h->descriptor_size);
    168   GET3(h->descriptor_signature_offset);
    169   GET3(h->descriptor_signature_size);
    170   GET3(h->pubkey_offset);
    171   GET3(h->pubkey_size);
    172 #undef GET3
    173   return DIST_OK;
    174 }
    175 
    176 void dist_kpkg3_encode_index_record(uint8_t out[DIST_KPKG3_INDEX_RECORD_SIZE],
    177                                     const DistKpkg3IndexRecord* r) {
    178   memset(out, 0, DIST_KPKG3_INDEX_RECORD_SIZE);
    179   memcpy(out + 0u, r->blob_id, DIST_BLAKE2B_LEN);
    180   put_u64le(out + 32u, r->chunk_index);
    181   put_u64le(out + 40u, r->content_offset);
    182   put_u64le(out + 48u, r->stored_size);
    183   put_u64le(out + 56u, r->raw_size);
    184   put_u32le(out + 64u, r->compression);
    185   put_u32le(out + 68u, 0u);
    186   memcpy(out + 72u, r->stored_hash, DIST_BLAKE2B_LEN);
    187   memcpy(out + 104u, r->raw_hash, DIST_BLAKE2B_LEN);
    188   memcpy(out + 136u, r->leaf_hash, DIST_BLAKE2B_LEN);
    189 }
    190 
    191 int dist_kpkg3_decode_index_record(const uint8_t* data, size_t len,
    192                                    DistKpkg3IndexRecord* r) {
    193   if (len < DIST_KPKG3_INDEX_RECORD_SIZE) return DIST_ERR;
    194   if (get_u32le(data + 68u) != 0u) return DIST_ERR;
    195   memcpy(r->blob_id, data + 0u, DIST_BLAKE2B_LEN);
    196   r->chunk_index = get_u64le(data + 32u);
    197   r->content_offset = get_u64le(data + 40u);
    198   r->stored_size = get_u64le(data + 48u);
    199   r->raw_size = get_u64le(data + 56u);
    200   r->compression = get_u32le(data + 64u);
    201   memcpy(r->stored_hash, data + 72u, DIST_BLAKE2B_LEN);
    202   memcpy(r->raw_hash, data + 104u, DIST_BLAKE2B_LEN);
    203   memcpy(r->leaf_hash, data + 136u, DIST_BLAKE2B_LEN);
    204   return DIST_OK;
    205 }
    206 
    207 int dist_kpkg3_descriptor_emit(KitWriter* out, const DistKpkg3Descriptor* d) {
    208   size_t i;
    209   if (emit(out, "kit-encoding 3\n") != DIST_OK) return DIST_ERR;
    210   if (emit_hex(out, "package-id", d->package_id) != DIST_OK) return DIST_ERR;
    211   if (emit_kv(out, "format", "kpkg") != DIST_OK) return DIST_ERR;
    212   if (emit_kv(out, "hash", DIST_KPKG3_HASH) != DIST_OK) return DIST_ERR;
    213   if (emit_kv(out, "tree", DIST_KPKG3_TREE_FORMAT) != DIST_OK) return DIST_ERR;
    214   if (emit_kv(out, "blob", DIST_KPKG3_BLOB_FORMAT) != DIST_OK) return DIST_ERR;
    215   if (emit_u64(out, "chunk-size", d->chunk_size) != DIST_OK) return DIST_ERR;
    216   if (emit_u64(out, "alignment", d->alignment) != DIST_OK) return DIST_ERR;
    217   if (emit_u64(out, "tree-offset", d->tree_offset) != DIST_OK) return DIST_ERR;
    218   if (emit_u64(out, "tree-size", d->tree_size) != DIST_OK) return DIST_ERR;
    219   if (emit_hex(out, "tree-root", d->tree_root) != DIST_OK) return DIST_ERR;
    220   if (emit_u64(out, "index-offset", d->index_offset) != DIST_OK)
    221     return DIST_ERR;
    222   if (emit_u64(out, "index-size", d->index_size) != DIST_OK) return DIST_ERR;
    223   if (emit_u64(out, "index-bytes", d->index_bytes) != DIST_OK) return DIST_ERR;
    224   if (emit_hex(out, "index-root", d->index_root) != DIST_OK) return DIST_ERR;
    225   if (d->index_url[0] && emit_kv(out, "index-url", d->index_url) != DIST_OK)
    226     return DIST_ERR;
    227   if (emit_u64(out, "content-offset", d->content_offset) != DIST_OK)
    228     return DIST_ERR;
    229   if (emit_u64(out, "content-size", d->content_size) != DIST_OK)
    230     return DIST_ERR;
    231   if (emit_hex(out, "content-root", d->content_root) != DIST_OK)
    232     return DIST_ERR;
    233 
    234   for (i = 0; i < d->n_trees; ++i) {
    235     if (emit(out, "\n[tree-object]\n") != DIST_OK) return DIST_ERR;
    236     if (emit_hex(out, "tree", d->trees[i].tree) != DIST_OK) return DIST_ERR;
    237     if (d->trees[i].embedded) {
    238       if (emit_u64(out, "offset", d->trees[i].offset) != DIST_OK)
    239         return DIST_ERR;
    240       if (emit_u64(out, "size", d->trees[i].size) != DIST_OK) return DIST_ERR;
    241     }
    242     if (emit_hex(out, "blake2b", d->trees[i].blake2b) != DIST_OK)
    243       return DIST_ERR;
    244     if (d->trees[i].url[0] && emit_kv(out, "url", d->trees[i].url) != DIST_OK)
    245       return DIST_ERR;
    246   }
    247 
    248   for (i = 0; i < d->n_chunk_sources; ++i) {
    249     if (emit(out, "\n[chunk-source]\n") != DIST_OK) return DIST_ERR;
    250     if (d->chunk_sources[i].kind == DIST_KPKG3_CHUNK_SOURCE_EMBEDDED) {
    251       if (emit_kv(out, "kind", "embedded") != DIST_OK) return DIST_ERR;
    252     } else if (d->chunk_sources[i].kind ==
    253                DIST_KPKG3_CHUNK_SOURCE_URL_TEMPLATE) {
    254       if (!d->chunk_sources[i].tmpl[0]) return DIST_ERR;
    255       if (emit_kv(out, "kind", "url-template") != DIST_OK) return DIST_ERR;
    256       if (emit_kv(out, "template", d->chunk_sources[i].tmpl) != DIST_OK)
    257         return DIST_ERR;
    258     } else {
    259       return DIST_ERR;
    260     }
    261   }
    262   return DIST_OK;
    263 }
    264 
    265 typedef enum Kpkg3DescriptorSection {
    266   KPKG3_DESC_TOP = 0,
    267   KPKG3_DESC_TREE_OBJECT = 1,
    268   KPKG3_DESC_CHUNK_SOURCE = 2,
    269 } Kpkg3DescriptorSection;
    270 
    271 #define KPKG3_TOP_PACKAGE_ID (1u << 0)
    272 #define KPKG3_TOP_FORMAT (1u << 1)
    273 #define KPKG3_TOP_HASH (1u << 2)
    274 #define KPKG3_TOP_TREE (1u << 3)
    275 #define KPKG3_TOP_BLOB (1u << 4)
    276 #define KPKG3_TOP_CHUNK_SIZE (1u << 5)
    277 #define KPKG3_TOP_ALIGNMENT (1u << 6)
    278 #define KPKG3_TOP_TREE_OFFSET (1u << 7)
    279 #define KPKG3_TOP_TREE_SIZE (1u << 8)
    280 #define KPKG3_TOP_TREE_ROOT (1u << 9)
    281 #define KPKG3_TOP_INDEX_OFFSET (1u << 10)
    282 #define KPKG3_TOP_INDEX_SIZE (1u << 11)
    283 #define KPKG3_TOP_INDEX_BYTES (1u << 12)
    284 #define KPKG3_TOP_INDEX_ROOT (1u << 13)
    285 #define KPKG3_TOP_CONTENT_OFFSET (1u << 14)
    286 #define KPKG3_TOP_CONTENT_SIZE (1u << 15)
    287 #define KPKG3_TOP_CONTENT_ROOT (1u << 16)
    288 #define KPKG3_TOP_INDEX_URL (1u << 17)
    289 #define KPKG3_TOP_REQUIRED ((1u << 17) - 1u)
    290 
    291 #define KPKG3_TREE_SEEN_TREE (1u << 0)
    292 #define KPKG3_TREE_SEEN_OFFSET (1u << 1)
    293 #define KPKG3_TREE_SEEN_SIZE (1u << 2)
    294 #define KPKG3_TREE_SEEN_BLAKE2B (1u << 3)
    295 #define KPKG3_TREE_SEEN_URL (1u << 4)
    296 
    297 #define KPKG3_CHUNK_SEEN_KIND (1u << 0)
    298 #define KPKG3_CHUNK_SEEN_TEMPLATE (1u << 1)
    299 
    300 static int finish_kpkg3_section(Kpkg3DescriptorSection section, uint32_t seen,
    301                                 DistKpkg3Descriptor* d, char* err,
    302                                 size_t errcap) {
    303   if (section == KPKG3_DESC_TREE_OBJECT) {
    304     DistKpkg3TreeObject* tree = &d->trees[d->n_trees - 1u];
    305     int has_offset = (seen & KPKG3_TREE_SEEN_OFFSET) != 0;
    306     int has_size = (seen & KPKG3_TREE_SEEN_SIZE) != 0;
    307     if ((seen & KPKG3_TREE_SEEN_TREE) == 0 ||
    308         (seen & KPKG3_TREE_SEEN_BLAKE2B) == 0)
    309       return set_err(err, errcap, "missing tree-object field");
    310     if (has_offset != has_size)
    311       return set_err(err, errcap, "partial tree-object embedded range");
    312     tree->embedded = has_offset;
    313   } else if (section == KPKG3_DESC_CHUNK_SOURCE) {
    314     DistKpkg3ChunkSource* source = &d->chunk_sources[d->n_chunk_sources - 1u];
    315     if ((seen & KPKG3_CHUNK_SEEN_KIND) == 0)
    316       return set_err(err, errcap, "missing chunk-source kind");
    317     if (source->kind == DIST_KPKG3_CHUNK_SOURCE_URL_TEMPLATE) {
    318       if ((seen & KPKG3_CHUNK_SEEN_TEMPLATE) == 0)
    319         return set_err(err, errcap, "missing chunk-source template");
    320     } else if (source->kind == DIST_KPKG3_CHUNK_SOURCE_EMBEDDED) {
    321       if ((seen & KPKG3_CHUNK_SEEN_TEMPLATE) != 0)
    322         return set_err(err, errcap, "embedded chunk-source has template");
    323     } else {
    324       return set_err(err, errcap, "bad chunk-source kind");
    325     }
    326   }
    327   return DIST_OK;
    328 }
    329 
    330 static int parse_kpkg3_top_key(DistKpkg3Descriptor* d, uint32_t* seen,
    331                                const char* key, const char* val, char* err,
    332                                size_t errcap) {
    333   if (strcmp(key, "package-id") == 0) {
    334     if (seen_once(seen, KPKG3_TOP_PACKAGE_ID) != DIST_OK ||
    335         parse_hex32(d->package_id, val) != DIST_OK)
    336       return set_err(err, errcap, "bad package-id");
    337   } else if (strcmp(key, "format") == 0) {
    338     if (seen_once(seen, KPKG3_TOP_FORMAT) != DIST_OK ||
    339         strcmp(val, "kpkg") != 0)
    340       return set_err(err, errcap, "bad format");
    341   } else if (strcmp(key, "hash") == 0) {
    342     if (seen_once(seen, KPKG3_TOP_HASH) != DIST_OK ||
    343         strcmp(val, DIST_KPKG3_HASH) != 0)
    344       return set_err(err, errcap, "bad hash algorithm");
    345   } else if (strcmp(key, "tree") == 0) {
    346     if (seen_once(seen, KPKG3_TOP_TREE) != DIST_OK ||
    347         strcmp(val, DIST_KPKG3_TREE_FORMAT) != 0)
    348       return set_err(err, errcap, "bad tree format");
    349   } else if (strcmp(key, "blob") == 0) {
    350     if (seen_once(seen, KPKG3_TOP_BLOB) != DIST_OK ||
    351         strcmp(val, DIST_KPKG3_BLOB_FORMAT) != 0)
    352       return set_err(err, errcap, "bad blob format");
    353   } else if (strcmp(key, "chunk-size") == 0) {
    354     if (seen_once(seen, KPKG3_TOP_CHUNK_SIZE) != DIST_OK ||
    355         parse_u64_strict(val, &d->chunk_size) != DIST_OK || d->chunk_size == 0)
    356       return set_err(err, errcap, "bad chunk-size");
    357   } else if (strcmp(key, "alignment") == 0) {
    358     if (seen_once(seen, KPKG3_TOP_ALIGNMENT) != DIST_OK ||
    359         parse_u64_strict(val, &d->alignment) != DIST_OK || d->alignment == 0)
    360       return set_err(err, errcap, "bad alignment");
    361   } else if (strcmp(key, "tree-offset") == 0) {
    362     if (seen_once(seen, KPKG3_TOP_TREE_OFFSET) != DIST_OK ||
    363         parse_u64_strict(val, &d->tree_offset) != DIST_OK)
    364       return set_err(err, errcap, "bad tree-offset");
    365   } else if (strcmp(key, "tree-size") == 0) {
    366     if (seen_once(seen, KPKG3_TOP_TREE_SIZE) != DIST_OK ||
    367         parse_u64_strict(val, &d->tree_size) != DIST_OK)
    368       return set_err(err, errcap, "bad tree-size");
    369   } else if (strcmp(key, "tree-root") == 0) {
    370     if (seen_once(seen, KPKG3_TOP_TREE_ROOT) != DIST_OK ||
    371         parse_hex32(d->tree_root, val) != DIST_OK)
    372       return set_err(err, errcap, "bad tree-root");
    373   } else if (strcmp(key, "index-offset") == 0) {
    374     if (seen_once(seen, KPKG3_TOP_INDEX_OFFSET) != DIST_OK ||
    375         parse_u64_strict(val, &d->index_offset) != DIST_OK)
    376       return set_err(err, errcap, "bad index-offset");
    377   } else if (strcmp(key, "index-size") == 0) {
    378     if (seen_once(seen, KPKG3_TOP_INDEX_SIZE) != DIST_OK ||
    379         parse_u64_strict(val, &d->index_size) != DIST_OK)
    380       return set_err(err, errcap, "bad index-size");
    381   } else if (strcmp(key, "index-bytes") == 0) {
    382     if (seen_once(seen, KPKG3_TOP_INDEX_BYTES) != DIST_OK ||
    383         parse_u64_strict(val, &d->index_bytes) != DIST_OK)
    384       return set_err(err, errcap, "bad index-bytes");
    385   } else if (strcmp(key, "index-root") == 0) {
    386     if (seen_once(seen, KPKG3_TOP_INDEX_ROOT) != DIST_OK ||
    387         parse_hex32(d->index_root, val) != DIST_OK)
    388       return set_err(err, errcap, "bad index-root");
    389   } else if (strcmp(key, "index-url") == 0) {
    390     if (seen_once(seen, KPKG3_TOP_INDEX_URL) != DIST_OK ||
    391         copy_field(d->index_url, sizeof d->index_url, val) != DIST_OK)
    392       return set_err(err, errcap, "bad index-url");
    393   } else if (strcmp(key, "content-offset") == 0) {
    394     if (seen_once(seen, KPKG3_TOP_CONTENT_OFFSET) != DIST_OK ||
    395         parse_u64_strict(val, &d->content_offset) != DIST_OK)
    396       return set_err(err, errcap, "bad content-offset");
    397   } else if (strcmp(key, "content-size") == 0) {
    398     if (seen_once(seen, KPKG3_TOP_CONTENT_SIZE) != DIST_OK ||
    399         parse_u64_strict(val, &d->content_size) != DIST_OK)
    400       return set_err(err, errcap, "bad content-size");
    401   } else if (strcmp(key, "content-root") == 0) {
    402     if (seen_once(seen, KPKG3_TOP_CONTENT_ROOT) != DIST_OK ||
    403         parse_hex32(d->content_root, val) != DIST_OK)
    404       return set_err(err, errcap, "bad content-root");
    405   } else {
    406     return set_err(err, errcap, "unknown encoding descriptor key");
    407   }
    408   return DIST_OK;
    409 }
    410 
    411 static int parse_kpkg3_tree_key(DistKpkg3TreeObject* tree, uint32_t* seen,
    412                                 const char* key, const char* val, char* err,
    413                                 size_t errcap) {
    414   if (strcmp(key, "tree") == 0) {
    415     if (seen_once(seen, KPKG3_TREE_SEEN_TREE) != DIST_OK ||
    416         parse_hex32(tree->tree, val) != DIST_OK)
    417       return set_err(err, errcap, "bad tree-object tree");
    418   } else if (strcmp(key, "offset") == 0) {
    419     if (seen_once(seen, KPKG3_TREE_SEEN_OFFSET) != DIST_OK ||
    420         parse_u64_strict(val, &tree->offset) != DIST_OK)
    421       return set_err(err, errcap, "bad tree-object offset");
    422   } else if (strcmp(key, "size") == 0) {
    423     if (seen_once(seen, KPKG3_TREE_SEEN_SIZE) != DIST_OK ||
    424         parse_u64_strict(val, &tree->size) != DIST_OK)
    425       return set_err(err, errcap, "bad tree-object size");
    426   } else if (strcmp(key, "blake2b") == 0) {
    427     if (seen_once(seen, KPKG3_TREE_SEEN_BLAKE2B) != DIST_OK ||
    428         parse_hex32(tree->blake2b, val) != DIST_OK)
    429       return set_err(err, errcap, "bad tree-object blake2b");
    430   } else if (strcmp(key, "url") == 0) {
    431     if (seen_once(seen, KPKG3_TREE_SEEN_URL) != DIST_OK ||
    432         copy_field(tree->url, sizeof tree->url, val) != DIST_OK)
    433       return set_err(err, errcap, "bad tree-object url");
    434   } else {
    435     return set_err(err, errcap, "unknown tree-object key");
    436   }
    437   return DIST_OK;
    438 }
    439 
    440 static int parse_kpkg3_chunk_key(DistKpkg3ChunkSource* source, uint32_t* seen,
    441                                  const char* key, const char* val, char* err,
    442                                  size_t errcap) {
    443   if (strcmp(key, "kind") == 0) {
    444     if (seen_once(seen, KPKG3_CHUNK_SEEN_KIND) != DIST_OK)
    445       return set_err(err, errcap, "bad chunk-source kind");
    446     if (strcmp(val, "embedded") == 0) {
    447       source->kind = DIST_KPKG3_CHUNK_SOURCE_EMBEDDED;
    448     } else if (strcmp(val, "url-template") == 0) {
    449       source->kind = DIST_KPKG3_CHUNK_SOURCE_URL_TEMPLATE;
    450     } else {
    451       return set_err(err, errcap, "bad chunk-source kind");
    452     }
    453   } else if (strcmp(key, "template") == 0) {
    454     if (seen_once(seen, KPKG3_CHUNK_SEEN_TEMPLATE) != DIST_OK ||
    455         copy_field(source->tmpl, sizeof source->tmpl, val) != DIST_OK)
    456       return set_err(err, errcap, "bad chunk-source template");
    457   } else {
    458     return set_err(err, errcap, "unknown chunk-source key");
    459   }
    460   return DIST_OK;
    461 }
    462 
    463 int dist_kpkg3_descriptor_parse(const uint8_t* data, size_t len,
    464                                 DistKpkg3Descriptor* d, char* err,
    465                                 size_t errcap) {
    466   size_t pos = 0;
    467   int first = 1;
    468   uint32_t top_seen = 0, section_seen = 0;
    469   Kpkg3DescriptorSection section = KPKG3_DESC_TOP;
    470   memset(d, 0, sizeof *d);
    471   while (pos < len) {
    472     char buf[DESC_LINE_MAX], *t, *eq, *key, *val;
    473     size_t end = pos, n;
    474     while (end < len && data[end] != '\n') ++end;
    475     n = end - pos;
    476     if (n >= sizeof buf) return set_err(err, errcap, "line too long");
    477     memcpy(buf, data + pos, n);
    478     buf[n] = '\0';
    479     pos = (end < len) ? end + 1u : end;
    480     trim_trail(buf);
    481     if (first) {
    482       first = 0;
    483       if (strcmp(buf, "kit-encoding 3") != 0)
    484         return set_err(err, errcap, "bad encoding descriptor magic/version");
    485       continue;
    486     }
    487     t = trim_lead(buf);
    488     if (*t == '\0' || *t == '#') continue;
    489     if (*t == '[') {
    490       if (finish_kpkg3_section(section, section_seen, d, err, errcap) !=
    491           DIST_OK)
    492         return DIST_ERR;
    493       section_seen = 0;
    494       if (strcmp(t, "[tree-object]") == 0) {
    495         if (d->n_trees == DIST_MAX_OUTPUTS)
    496           return set_err(err, errcap, "too many tree-object sections");
    497         memset(&d->trees[d->n_trees], 0, sizeof d->trees[d->n_trees]);
    498         ++d->n_trees;
    499         section = KPKG3_DESC_TREE_OBJECT;
    500       } else if (strcmp(t, "[chunk-source]") == 0) {
    501         if (d->n_chunk_sources == DIST_MAX_OUTPUTS)
    502           return set_err(err, errcap, "too many chunk-source sections");
    503         memset(&d->chunk_sources[d->n_chunk_sources], 0,
    504                sizeof d->chunk_sources[d->n_chunk_sources]);
    505         ++d->n_chunk_sources;
    506         section = KPKG3_DESC_CHUNK_SOURCE;
    507       } else {
    508         return set_err(err, errcap, "unknown encoding descriptor section");
    509       }
    510       continue;
    511     }
    512     eq = strchr(t, '=');
    513     if (!eq) return set_err(err, errcap, "expected key = value");
    514     *eq = '\0';
    515     key = t;
    516     trim_trail(key);
    517     val = trim_lead(eq + 1);
    518     if (section == KPKG3_DESC_TOP) {
    519       if (parse_kpkg3_top_key(d, &top_seen, key, val, err, errcap) != DIST_OK)
    520         return DIST_ERR;
    521     } else if (section == KPKG3_DESC_TREE_OBJECT) {
    522       if (parse_kpkg3_tree_key(&d->trees[d->n_trees - 1u], &section_seen, key,
    523                                val, err, errcap) != DIST_OK)
    524         return DIST_ERR;
    525     } else {
    526       if (parse_kpkg3_chunk_key(&d->chunk_sources[d->n_chunk_sources - 1u],
    527                                 &section_seen, key, val, err,
    528                                 errcap) != DIST_OK)
    529         return DIST_ERR;
    530     }
    531   }
    532   if (first) return set_err(err, errcap, "empty encoding descriptor");
    533   if (finish_kpkg3_section(section, section_seen, d, err, errcap) != DIST_OK)
    534     return DIST_ERR;
    535   if ((top_seen & KPKG3_TOP_REQUIRED) != KPKG3_TOP_REQUIRED)
    536     return set_err(err, errcap, "missing required encoding descriptor field");
    537   if (d->index_size != 0 && d->index_size != d->index_bytes)
    538     return set_err(err, errcap, "embedded index size mismatch");
    539   return DIST_OK;
    540 }
    541 
    542 const char* dist_kpkg_compression_name(uint32_t c) {
    543   if (c == DIST_KPKG_COMP_NONE) return "none";
    544   if (c == DIST_KPKG_COMP_LZ4_BLOCK_V1) return "lz4-block-v1";
    545   return NULL;
    546 }
    547 
    548 int dist_kpkg_compression_parse(const char* s, uint32_t* out) {
    549   if (strcmp(s, "none") == 0) {
    550     *out = DIST_KPKG_COMP_NONE;
    551     return DIST_OK;
    552   }
    553   if (strcmp(s, "lz4-block-v1") == 0) {
    554     *out = DIST_KPKG_COMP_LZ4_BLOCK_V1;
    555     return DIST_OK;
    556   }
    557   return DIST_ERR;
    558 }