kit

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

kpkg.c (21962B)


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