LCOV - code coverage report
Current view: top level - src/core - decoder.c (source / functions) Coverage Total Hit
Test: lcov.info Lines: 98.5 % 543 535
Test Date: 2026-09-17 22:59:44 Functions: 100.0 % 21 21
Branches: 87.9 % 322 283

             Branch data     Line data    Source code
       1                 :             : /**
       2                 :             :  * @file decoder.c
       3                 :             :  * @brief Dictionary lifecycle: open compiled .trp buffer, lookup keys.
       4                 :             :  *
       5                 :             :  * Copyright (c) 2026 M. A. Chatterjee <deftio at deftio dot com>
       6                 :             :  * BSD-2-Clause — see LICENSE.txt
       7                 :             :  */
       8                 :             : 
       9                 :             : #include "core_internal.h"
      10                 :             : 
      11                 :             : /* ── Internal: parse trie config and symbol table ───────────────────── */
      12                 :             : 
      13                 :        3362 : static tp_result parse_trie_config(tp_bitstream_reader *r, tp_dict *dict)
      14                 :             : {
      15                 :             :     uint64_t val;
      16                 :             :     tp_result rc;
      17                 :             : 
      18                 :             :     /* bits_per_symbol (4 bits) */
      19                 :        3362 :     rc = tp_bs_read_bits(r, 4, &val);
      20         [ +  + ]:        3362 :     if (rc != TP_OK)
      21                 :           2 :         return rc;
      22                 :             :     /* Symbol codes index 256-entry maps, so a symbol may not be wider than a
      23                 :             :        byte; 0 would make the trie unreadable. */
      24   [ +  +  +  + ]:        3360 :     if (val < 1 || val > 8)
      25                 :           6 :         return TP_ERR_ALPHABET;
      26                 :        3354 :     dict->sym.bits_per_symbol = (uint8_t)val;
      27                 :        3354 :     dict->info.bits_per_symbol = (uint8_t)val;
      28                 :             : 
      29                 :             :     /* symbol_count (8 bits) */
      30                 :        3354 :     rc = tp_bs_read_bits(r, 8, &val);
      31         [ +  + ]:        3354 :     if (rc != TP_OK)
      32                 :           3 :         return rc;
      33                 :             :     /* Must leave room for the control codes and fit in bits_per_symbol. */
      34   [ +  +  +  + ]:        3351 :     if (val < TP_NUM_CONTROL_CODES || val > (1u << dict->sym.bits_per_symbol))
      35                 :          19 :         return TP_ERR_ALPHABET;
      36                 :        3332 :     dict->sym.symbol_count = (uint16_t)val;
      37                 :             : 
      38                 :             :     /* special_symbol_map: 6 control codes */
      39                 :        3332 :     memset(dict->sym.code_is_ctrl, 0, sizeof(dict->sym.code_is_ctrl));
      40         [ +  + ]:       23298 :     for (int c = 0; c < TP_NUM_CONTROL_CODES; c++) {
      41                 :       19975 :         rc = tp_bs_read_bits(r, dict->sym.bits_per_symbol, &val);
      42         [ +  + ]:       19975 :         if (rc != TP_OK)
      43                 :           9 :             return rc;
      44                 :       19966 :         dict->sym.ctrl_codes[c] = (uint32_t)val;
      45         [ +  - ]:       19966 :         if (val < 256)
      46                 :       19966 :             dict->sym.code_is_ctrl[val] = true;
      47                 :             :     }
      48                 :             : 
      49                 :             :     /* Read symbol table */
      50                 :        3323 :     memset(dict->sym.reverse_map, 0, sizeof(dict->sym.reverse_map));
      51                 :        3323 :     memset(dict->sym.symbol_map, 0, sizeof(dict->sym.symbol_map));
      52                 :        3323 :     uint32_t max_code = dict->sym.symbol_count;
      53         [ +  + ]:       57658 :     for (uint32_t code = TP_NUM_CONTROL_CODES; code < max_code; code++) {
      54                 :             :         uint64_t cp;
      55                 :       54442 :         rc = tp_bs_read_varint_u(r, &cp);
      56         [ +  + ]:       54442 :         if (rc != TP_OK)
      57                 :         107 :             return rc;
      58   [ +  -  +  + ]:       54335 :         if (code < 256 && cp < 256) {
      59                 :       54212 :             dict->sym.reverse_map[code] = (uint8_t)cp;
      60                 :       54212 :             dict->sym.symbol_map[cp] = code;
      61                 :             :         }
      62                 :             :     }
      63                 :             : 
      64                 :        3216 :     return TP_OK;
      65                 :             : }
      66                 :             : 
      67                 :             : /* ── Open / Close ────────────────────────────────────────────────────── */
      68                 :             : 
      69                 :        3462 : static tp_result dict_open_impl(tp_dict **out, const uint8_t *buf, size_t len, bool check_crc)
      70                 :             : {
      71   [ +  +  +  + ]:        3462 :     if (!out || !buf)
      72                 :           2 :         return TP_ERR_INVALID_PARAM;
      73         [ +  + ]:        3460 :     if (len < TP_HEADER_SIZE)
      74                 :           7 :         return TP_ERR_TRUNCATED;
      75                 :             : 
      76                 :             :     /* Parse header */
      77                 :             :     /* Allocation failure paths are excluded from coverage (LCOV_EXCL). */
      78                 :        3453 :     tp_bitstream_reader *r = NULL;
      79                 :        3453 :     tp_result rc = tp_bs_reader_create(&r, buf, (uint64_t)len * 8);
      80                 :        3453 :     if (rc != TP_OK) /* LCOV_EXCL_BR_LINE */
      81                 :             :         return rc;   /* LCOV_EXCL_LINE */
      82                 :             : 
      83                 :             :     tp_header hdr;
      84                 :        3453 :     rc = tp_header_read(r, &hdr);
      85         [ +  + ]:        3453 :     if (rc != TP_OK) {
      86                 :          19 :         tp_bs_reader_destroy(&r);
      87                 :          19 :         return rc;
      88                 :             :     }
      89                 :             : 
      90                 :             :     /* CRC verification */
      91   [ +  +  +  - ]:        3434 :     if (check_crc && len >= 4) {
      92                 :        2590 :         size_t crc_data_len = len - 4;
      93                 :        2590 :         uint32_t expected_crc =
      94                 :        2590 :             ((uint32_t)buf[crc_data_len] << 24) | ((uint32_t)buf[crc_data_len + 1] << 16) |
      95                 :        2590 :             ((uint32_t)buf[crc_data_len + 2] << 8) | ((uint32_t)buf[crc_data_len + 3]);
      96                 :        2590 :         uint32_t actual_crc = tp_crc32(buf, crc_data_len);
      97         [ +  + ]:        2590 :         if (actual_crc != expected_crc) {
      98                 :          70 :             tp_bs_reader_destroy(&r);
      99                 :          70 :             return TP_ERR_CORRUPT;
     100                 :             :         }
     101                 :             :     }
     102                 :             : 
     103                 :             :     /* Check for unsupported features */
     104         [ +  + ]:        3364 :     if (hdr.flags & TP_FLAG_HAS_SUFFIX_TABLE) {
     105                 :           2 :         tp_bs_reader_destroy(&r);
     106                 :           2 :         return TP_ERR_VERSION;
     107                 :             :     }
     108                 :             : 
     109                 :             :     /* Allocate dict */
     110                 :        3362 :     tp_dict *dict = calloc(1, sizeof(*dict));
     111                 :        3362 :     if (!dict) { /* LCOV_EXCL_BR_LINE */
     112                 :             :         /* LCOV_EXCL_START */
     113                 :             :         tp_bs_reader_destroy(&r);
     114                 :             :         return TP_ERR_ALLOC;
     115                 :             :         /* LCOV_EXCL_STOP */
     116                 :             :     }
     117                 :             : 
     118                 :        3362 :     dict->buf = buf;
     119                 :        3362 :     dict->len = len;
     120                 :        3362 :     dict->hdr = hdr;
     121                 :             : 
     122                 :             :     /* Populate info */
     123                 :        3362 :     dict->info.format_version_major = hdr.version_major;
     124                 :        3362 :     dict->info.format_version_minor = hdr.version_minor;
     125                 :        3362 :     dict->info.num_keys = hdr.num_keys;
     126                 :        3362 :     dict->info.has_values = (hdr.flags & TP_FLAG_HAS_VALUES) != 0;
     127                 :        3362 :     dict->info.has_suffix_table = false;
     128                 :        3362 :     dict->info.compact_mode = (hdr.flags & TP_FLAG_COMPACT_MODE) != 0;
     129                 :        3362 :     dict->info.total_bytes = len;
     130                 :        3362 :     dict->info.checksum_type = TP_CHECKSUM_CRC32;
     131                 :        3362 :     dict->info.trie_mode = TP_ADDR_BIT;
     132                 :        3362 :     dict->info.value_mode = TP_ADDR_BYTE;
     133                 :             : 
     134                 :             :     /* Parse trie config and symbol table */
     135                 :             :     /* Reader is at bit 256 (byte 32, start of data stream) */
     136                 :        3362 :     rc = parse_trie_config(r, dict);
     137         [ +  + ]:        3362 :     if (rc != TP_OK) {
     138                 :         146 :         tp_bs_reader_destroy(&r);
     139                 :         146 :         free(dict);
     140                 :         146 :         return rc;
     141                 :             :     }
     142                 :             : 
     143                 :        3216 :     uint64_t data_start = (uint64_t)TP_HEADER_SIZE * 8;
     144                 :        3216 :     dict->trie_start = data_start + hdr.trie_data_offset;
     145                 :        3216 :     dict->value_start = data_start + hdr.value_store_offset;
     146                 :             : 
     147                 :        3216 :     tp_bs_reader_destroy(&r);
     148                 :        3216 :     *out = dict;
     149                 :        3216 :     return TP_OK;
     150                 :             : }
     151                 :             : 
     152                 :        2605 : tp_result tp_dict_open(tp_dict **out, const uint8_t *buf, size_t len)
     153                 :             : {
     154                 :        2605 :     return dict_open_impl(out, buf, len, true);
     155                 :             : }
     156                 :             : 
     157                 :         857 : tp_result tp_dict_open_unchecked(tp_dict **out, const uint8_t *buf, size_t len)
     158                 :             : {
     159                 :         857 :     return dict_open_impl(out, buf, len, false);
     160                 :             : }
     161                 :             : 
     162                 :        3240 : tp_result tp_dict_close(tp_dict **dict)
     163                 :             : {
     164         [ +  + ]:        3240 :     if (!dict)
     165                 :           1 :         return TP_ERR_INVALID_PARAM;
     166                 :        3239 :     free(*dict);
     167                 :        3239 :     *dict = NULL;
     168                 :        3239 :     return TP_OK;
     169                 :             : }
     170                 :             : 
     171                 :             : /* ── Query ───────────────────────────────────────────────────────────── */
     172                 :             : 
     173                 :        2315 : uint32_t tp_dict_count(const tp_dict *dict)
     174                 :             : {
     175         [ +  + ]:        2315 :     if (!dict)
     176                 :           1 :         return 0;
     177                 :        2314 :     return dict->info.num_keys;
     178                 :             : }
     179                 :             : 
     180                 :             : /* ── Lookup ──────────────────────────────────────────────────────────── */
     181                 :             : 
     182                 :       22521 : tp_result tp_dict_lookup(const tp_dict *dict, const char *key, tp_value *val)
     183                 :             : {
     184   [ +  +  -  + ]:       22521 :     if (!dict || !key)
     185                 :           1 :         return TP_ERR_INVALID_PARAM;
     186                 :       22520 :     return tp_dict_lookup_n(dict, key, strlen(key), val);
     187                 :             : }
     188                 :             : 
     189                 :       41195 : tp_result tp_dict_lookup_n(const tp_dict *dict, const char *key, size_t key_len, tp_value *val)
     190                 :             : {
     191   [ +  +  -  + ]:       41195 :     if (!dict || !key)
     192                 :           1 :         return TP_ERR_INVALID_PARAM;
     193                 :             : 
     194                 :             :     /* Empty dictionary: nothing to find */
     195         [ +  + ]:       41194 :     if (dict->info.num_keys == 0)
     196                 :           2 :         return TP_ERR_NOT_FOUND;
     197                 :             : 
     198                 :       41192 :     tp_bitstream_reader *r = NULL;
     199                 :       41192 :     tp_result rc = tp_bs_reader_create(&r, dict->buf, (uint64_t)dict->len * 8);
     200                 :       41192 :     if (rc != TP_OK) /* LCOV_EXCL_BR_LINE */
     201                 :             :         return rc;   /* LCOV_EXCL_LINE */
     202                 :             : 
     203                 :       41192 :     rc = tp_bs_reader_seek(r, dict->trie_start);
     204         [ +  + ]:       41192 :     if (rc != TP_OK) {
     205                 :           1 :         tp_bs_reader_destroy(&r);
     206                 :             :         return rc; /* LCOV_EXCL_LINE */
     207                 :             :     }
     208                 :             : 
     209                 :       41191 :     uint8_t bps = dict->sym.bits_per_symbol;
     210                 :       41191 :     size_t key_idx = 0;
     211                 :       41191 :     uint32_t value_index = 0;
     212                 :       41191 :     bool found_value = false;
     213                 :       41191 :     bool expect_branch = false; /* set after END/END_VAL when key not consumed */
     214                 :             : 
     215                 :      400979 :     while (true) {
     216                 :             :         uint64_t sym_raw;
     217                 :      442170 :         rc = tp_bs_read_bits(r, bps, &sym_raw);
     218         [ +  + ]:      442170 :         if (rc != TP_OK) {
     219                 :          10 :             tp_bs_reader_destroy(&r);
     220                 :             :             /* If we were expecting a BRANCH after a terminal but hit
     221                 :             :                EOF, the key doesn't exist. */
     222         [ +  + ]:          10 :             if (expect_branch)
     223                 :       14943 :                 return TP_ERR_NOT_FOUND;
     224                 :           9 :             return rc;
     225                 :             :         }
     226                 :      442160 :         uint32_t sym = (uint32_t)sym_raw;
     227                 :             : 
     228         [ +  + ]:      442160 :         if (sym == dict->sym.ctrl_codes[TP_CTRL_END]) {
     229         [ +  + ]:       21577 :             if (key_idx == key_len) {
     230                 :       12042 :                 tp_bs_reader_destroy(&r);
     231         [ +  + ]:       12042 :                 if (val)
     232                 :       12041 :                     *val = tp_value_null();
     233                 :       12042 :                 return TP_OK;
     234                 :             :             }
     235                 :             :             /* Key not fully consumed: a BRANCH may follow this terminal
     236                 :             :                for children sharing this prefix. */
     237                 :        9535 :             expect_branch = true;
     238                 :      400979 :             continue;
     239                 :             :         }
     240                 :             : 
     241         [ +  + ]:      420583 :         if (sym == dict->sym.ctrl_codes[TP_CTRL_END_VAL]) {
     242                 :             :             uint64_t vi;
     243                 :       36205 :             rc = tp_bs_read_varint_u(r, &vi);
     244         [ +  + ]:       36205 :             if (rc != TP_OK) {
     245                 :           5 :                 tp_bs_reader_destroy(&r);
     246                 :           5 :                 return rc;
     247                 :             :             }
     248         [ +  + ]:       36200 :             if (key_idx == key_len) {
     249                 :       26248 :                 value_index = (uint32_t)vi;
     250                 :       26248 :                 found_value = true;
     251                 :       26248 :                 break;
     252                 :             :             }
     253                 :             :             /* Key not fully consumed: a BRANCH may follow. */
     254                 :        9952 :             expect_branch = true;
     255                 :        9952 :             continue;
     256                 :             :         }
     257                 :             : 
     258         [ +  + ]:      384378 :         if (sym == dict->sym.ctrl_codes[TP_CTRL_BRANCH]) {
     259                 :      156149 :             expect_branch = false;
     260                 :             :             uint64_t child_count;
     261                 :      156149 :             rc = tp_bs_read_varint_u(r, &child_count);
     262         [ +  + ]:      156149 :             if (rc != TP_OK) {
     263                 :           4 :                 tp_bs_reader_destroy(&r);
     264                 :        1559 :                 return rc;
     265                 :             :             }
     266                 :             : 
     267         [ +  + ]:      156145 :             if (key_idx >= key_len) {
     268                 :         114 :                 tp_bs_reader_destroy(&r);
     269                 :         114 :                 return TP_ERR_NOT_FOUND;
     270                 :             :             }
     271                 :             : 
     272                 :      156031 :             uint8_t target = (uint8_t)key[key_idx];
     273                 :      156031 :             bool found_child = false;
     274                 :             : 
     275         [ +  + ]:      588550 :             for (uint64_t ci = 0; ci < child_count; ci++) {
     276                 :      587546 :                 bool has_skip = (ci < child_count - 1);
     277                 :      587546 :                 uint64_t skip_dist = 0;
     278                 :             : 
     279         [ +  + ]:      587546 :                 if (has_skip) {
     280                 :             :                     /* Read SKIP control code */
     281                 :             :                     uint64_t skip_sym;
     282                 :      556678 :                     rc = tp_bs_read_bits(r, bps, &skip_sym);
     283         [ +  + ]:      556678 :                     if (rc != TP_OK) {
     284                 :          11 :                         tp_bs_reader_destroy(&r);
     285                 :          18 :                         return rc;
     286                 :             :                     }
     287                 :             :                     /* Read skip distance */
     288                 :      556667 :                     rc = tp_bs_read_varint_u(r, &skip_dist);
     289         [ +  + ]:      556667 :                     if (rc != TP_OK) {
     290                 :           7 :                         tp_bs_reader_destroy(&r);
     291                 :           7 :                         return rc;
     292                 :             :                     }
     293                 :             :                 }
     294                 :             : 
     295                 :             :                 /* Peek at first symbol of this child */
     296                 :             :                 uint64_t child_first_sym;
     297                 :      587528 :                 rc = tp_bs_peek_bits(r, bps, &child_first_sym);
     298         [ +  + ]:      587528 :                 if (rc != TP_OK) {
     299                 :           5 :                     tp_bs_reader_destroy(&r);
     300                 :           5 :                     return rc;
     301                 :             :                 }
     302                 :             : 
     303                 :             :                 /* Determine if this is a regular symbol */
     304                 :      587523 :                 uint32_t csym = (uint32_t)child_first_sym;
     305   [ +  -  +  + ]:      587523 :                 bool is_ctrl = (csym < 256) && dict->sym.code_is_ctrl[csym];
     306                 :             : 
     307         [ +  + ]:      587523 :                 if (!is_ctrl) {
     308                 :             :                     /* It's a regular symbol; check if it matches */
     309                 :      586943 :                     uint32_t expected_code = dict->sym.symbol_map[target];
     310         [ +  + ]:      586943 :                     if (csym == expected_code) {
     311                 :             :                         /* Match! Consume this symbol and continue */
     312                 :      154590 :                         tp_bs_reader_advance(r, bps);
     313                 :      154590 :                         key_idx++;
     314                 :      154590 :                         found_child = true;
     315                 :      154590 :                         break;
     316                 :             :                     }
     317                 :             :                 }
     318                 :             : 
     319                 :             :                 /* Check if it's an END/END_VAL (terminal with no more key
     320                 :             :                    chars). These can only match if target matches nothing,
     321                 :             :                    so skip to next sibling. */
     322                 :             : 
     323         [ +  + ]:      432933 :                 if (has_skip) {
     324                 :             :                     /* Skip to next sibling */
     325                 :      431959 :                     rc = tp_bs_reader_advance(r, skip_dist);
     326         [ +  + ]:      431959 :                     if (rc != TP_OK) {
     327                 :         414 :                         tp_bs_reader_destroy(&r);
     328                 :         414 :                         return rc;
     329                 :             :                     }
     330                 :             :                 }
     331                 :             :             }
     332                 :             : 
     333         [ +  + ]:      155594 :             if (!found_child) {
     334                 :        1004 :                 tp_bs_reader_destroy(&r);
     335                 :        1004 :                 return TP_ERR_NOT_FOUND;
     336                 :             :             }
     337                 :      154590 :             continue;
     338                 :             :         }
     339                 :             : 
     340                 :             :         /* If we expected a BRANCH after a terminal but got something
     341                 :             :            else, the key extends beyond a leaf node — not found. */
     342         [ +  + ]:      228229 :         if (expect_branch) {
     343                 :         217 :             tp_bs_reader_destroy(&r);
     344                 :         217 :             return TP_ERR_NOT_FOUND;
     345                 :             :         }
     346                 :             : 
     347                 :             :         /* Regular symbol */
     348   [ +  -  +  + ]:      228012 :         if (!dict->sym.code_is_ctrl[sym < 256 ? sym : 0]) {
     349         [ +  + ]:      227878 :             if (key_idx >= key_len) {
     350                 :          54 :                 tp_bs_reader_destroy(&r);
     351                 :          54 :                 return TP_ERR_NOT_FOUND;
     352                 :             :             }
     353                 :      227824 :             uint32_t expected_code = dict->sym.symbol_map[(uint8_t)key[key_idx]];
     354         [ +  + ]:      227824 :             if (sym != expected_code) {
     355                 :         922 :                 tp_bs_reader_destroy(&r);
     356                 :         922 :                 return TP_ERR_NOT_FOUND;
     357                 :             :             }
     358                 :      226902 :             key_idx++;
     359                 :      226902 :             continue;
     360                 :             :         }
     361                 :             : 
     362                 :             :         /* Unknown control code */
     363                 :         134 :         tp_bs_reader_destroy(&r);
     364                 :         134 :         return TP_ERR_INVALID_PARAM;
     365                 :             :     }
     366                 :             : 
     367                 :             :     /* Decode value if found */
     368   [ +  -  +  +  :       26248 :     if (found_value && val && dict->info.has_values) {
                   +  - ]
     369                 :             :         /* Seek to value store and skip to the right value */
     370                 :       26247 :         rc = tp_bs_reader_seek(r, dict->value_start);
     371         [ +  + ]:       26247 :         if (rc != TP_OK) {
     372                 :          72 :             tp_bs_reader_destroy(&r);
     373                 :          72 :             return rc;
     374                 :             :         }
     375                 :             : 
     376                 :             :         /* Skip value_index values */
     377         [ +  + ]:    50263373 :         for (uint32_t i = 0; i < value_index; i++) {
     378                 :             :             tp_value tmp;
     379                 :    50237454 :             rc = tp_value_decode(r, &tmp, dict->buf);
     380         [ +  + ]:    50237454 :             if (rc != TP_OK) {
     381                 :         256 :                 tp_bs_reader_destroy(&r);
     382                 :         256 :                 return rc;
     383                 :             :             }
     384                 :             :         }
     385                 :             : 
     386                 :       25919 :         rc = tp_value_decode(r, val, dict->buf);
     387                 :       25919 :         tp_bs_reader_destroy(&r);
     388                 :       25919 :         return rc;
     389                 :             :     }
     390                 :             : 
     391                 :             :     /* LCOV_EXCL_START — found_value is only set via CTRL_END_VAL which
     392                 :             :        implies has_values, so the condition above always takes priority. */
     393                 :             :     if (found_value && val) {
     394                 :             :         *val = tp_value_null();
     395                 :             :     }
     396                 :             :     /* LCOV_EXCL_STOP */
     397                 :             : 
     398                 :           1 :     tp_bs_reader_destroy(&r);
     399                 :           1 :     return TP_OK;
     400                 :             : }
     401                 :             : 
     402                 :       10037 : tp_result tp_dict_contains(const tp_dict *dict, const char *key, bool *out)
     403                 :             : {
     404   [ +  +  +  -  :       10037 :     if (!dict || !key || !out)
                   -  + ]
     405                 :           4 :         return TP_ERR_INVALID_PARAM;
     406                 :             : 
     407                 :             :     tp_value val;
     408                 :       10033 :     tp_result rc = tp_dict_lookup(dict, key, &val);
     409         [ +  + ]:       10033 :     if (rc == TP_OK) {
     410                 :       10021 :         *out = true;
     411                 :       10021 :         return TP_OK;
     412                 :             :     }
     413         [ +  + ]:          12 :     if (rc == TP_ERR_NOT_FOUND) {
     414                 :           7 :         *out = false;
     415                 :           7 :         return TP_OK;
     416                 :             :     }
     417                 :           5 :     return rc;
     418                 :             : }
     419                 :             : 
     420                 :             : /* ── Info ────────────────────────────────────────────────────────────── */
     421                 :             : 
     422                 :          10 : tp_result tp_dict_get_info(const tp_dict *dict, tp_dict_info *info)
     423                 :             : {
     424   [ +  +  -  + ]:          10 :     if (!dict || !info)
     425                 :           3 :         return TP_ERR_INVALID_PARAM;
     426                 :           7 :     *info = dict->info;
     427                 :           7 :     return TP_OK;
     428                 :             : }
     429                 :             : 
     430                 :             : /* ── Iteration ──────────────────────────────────────────────────────── */
     431                 :             : 
     432                 :             : /**
     433                 :             :  * The trie stream does not say where a subtree ends, so the walk carries the
     434                 :             :  * bound: a child with a SKIP ends at child_start + skip, the last child ends
     435                 :             :  * where its parent does, and the root ends at the value store. A terminal is
     436                 :             :  * followed by a BRANCH exactly when its subtree has not reached that bound.
     437                 :             :  */
     438                 :             : 
     439                 :       29891 : static tp_result iter_key_reserve(tp_iterator *it, size_t needed)
     440                 :             : {
     441         [ +  + ]:       29891 :     if (needed <= it->key_buf_cap)
     442                 :       29886 :         return TP_OK;
     443         [ +  - ]:           5 :     size_t cap = it->key_buf_cap ? it->key_buf_cap : 256;
     444         [ +  + ]:          11 :     while (cap < needed)
     445                 :           6 :         cap *= 2;
     446                 :           5 :     char *grown = realloc(it->key_buf, cap);
     447                 :           5 :     if (!grown)              /* LCOV_EXCL_BR_LINE */
     448                 :             :         return TP_ERR_ALLOC; /* LCOV_EXCL_LINE */
     449                 :           5 :     it->key_buf = grown;
     450                 :           5 :     it->key_buf_cap = cap;
     451                 :           5 :     return TP_OK;
     452                 :             : }
     453                 :             : 
     454                 :       28063 : static tp_result iter_push_key(tp_iterator *it, uint8_t byte)
     455                 :             : {
     456                 :       28063 :     tp_result rc = iter_key_reserve(it, it->key_len + 1);
     457                 :       28063 :     if (rc != TP_OK) /* LCOV_EXCL_BR_LINE */
     458                 :             :         return rc;   /* LCOV_EXCL_LINE */
     459                 :       28063 :     it->key_buf[it->key_len++] = (char)byte;
     460                 :       28063 :     return TP_OK;
     461                 :             : }
     462                 :             : 
     463                 :             : /** Decode the value at store index `index`, keeping the cursor in step. */
     464                 :        7825 : static tp_result iter_value_at(tp_iterator *it, uint64_t index, tp_value *val)
     465                 :             : {
     466         [ +  + ]:        7825 :     if (!it->dict->info.has_values) {
     467                 :          17 :         *val = tp_value_null();
     468                 :          17 :         return TP_OK;
     469                 :             :     }
     470                 :             : 
     471                 :        7808 :     tp_bitstream_reader *r = NULL;
     472                 :        7808 :     tp_result rc = tp_bs_reader_create(&r, it->dict->buf, (uint64_t)it->dict->len * 8);
     473                 :        7808 :     if (rc != TP_OK) /* LCOV_EXCL_BR_LINE */
     474                 :             :         return rc;   /* LCOV_EXCL_LINE */
     475                 :             : 
     476         [ +  + ]:        7808 :     if (index != it->value_index) {
     477                 :             :         /* Cursor is elsewhere — walk the store from the start. */
     478                 :        1271 :         rc = tp_bs_reader_seek(r, it->dict->value_start);
     479   [ +  +  +  + ]:        5806 :         for (uint64_t i = 0; rc == TP_OK && i < index; i++) {
     480                 :             :             tp_value skip;
     481                 :        4535 :             rc = tp_value_decode(r, &skip, it->dict->buf);
     482                 :             :         }
     483         [ +  + ]:        1271 :         if (rc != TP_OK) {
     484                 :         127 :             tp_bs_reader_destroy(&r);
     485                 :         127 :             return rc;
     486                 :             :         }
     487                 :        1144 :         it->value_pos = tp_bs_reader_position(r);
     488                 :        1144 :         it->value_index = index;
     489                 :             :     }
     490                 :             : 
     491                 :        7681 :     rc = tp_bs_reader_seek(r, it->value_pos);
     492         [ +  + ]:        7681 :     if (rc == TP_OK)
     493                 :        7631 :         rc = tp_value_decode(r, val, it->dict->buf);
     494         [ +  + ]:        7681 :     if (rc == TP_OK) {
     495                 :        7583 :         it->value_pos = tp_bs_reader_position(r);
     496                 :        7583 :         it->value_index = index + 1;
     497                 :             :     }
     498                 :        7681 :     tp_bs_reader_destroy(&r);
     499                 :        7681 :     return rc;
     500                 :             : }
     501                 :             : 
     502                 :        4411 : tp_result tp_dict_iterate(const tp_dict *dict, tp_iterator **out)
     503                 :             : {
     504   [ +  +  -  + ]:        4411 :     if (!dict || !out)
     505                 :           1 :         return TP_ERR_INVALID_PARAM;
     506                 :             : 
     507                 :        4410 :     tp_iterator *it = calloc(1, sizeof(*it));
     508                 :        4410 :     if (!it)                 /* LCOV_EXCL_BR_LINE */
     509                 :             :         return TP_ERR_ALLOC; /* LCOV_EXCL_LINE */
     510                 :             : 
     511                 :        4410 :     it->dict = dict;
     512                 :        4410 :     it->key_buf = malloc(256);
     513                 :        4410 :     if (!it->key_buf) { /* LCOV_EXCL_BR_LINE */
     514                 :             :         /* LCOV_EXCL_START */
     515                 :             :         free(it);
     516                 :             :         return TP_ERR_ALLOC;
     517                 :             :         /* LCOV_EXCL_STOP */
     518                 :             :     }
     519                 :        4410 :     it->key_buf_cap = 256;
     520                 :        4410 :     it->root_pos = dict->trie_start;
     521                 :        4410 :     it->root_end = dict->value_start;
     522                 :        4410 :     it->root_key_len = 0;
     523                 :        4410 :     tp_iter_reset(it);
     524                 :        4410 :     *out = it;
     525                 :        4410 :     return TP_OK;
     526                 :             : }
     527                 :             : 
     528                 :       11796 : tp_result tp_iter_next(tp_iterator *it, const char **key, size_t *key_len, tp_value *val)
     529                 :             : {
     530         [ +  + ]:       11796 :     if (!it)
     531                 :           1 :         return TP_ERR_INVALID_PARAM;
     532         [ +  + ]:       11795 :     if (it->done)
     533                 :        1607 :         return TP_ERR_EOF;
     534         [ +  + ]:       10188 :     if (it->dict->info.num_keys == 0) {
     535                 :           1 :         it->done = true;
     536                 :           1 :         return TP_ERR_EOF;
     537                 :             :     }
     538                 :             : 
     539                 :       10187 :     const tp_symbol_info *sym = &it->dict->sym;
     540                 :       10187 :     uint8_t bps = sym->bits_per_symbol;
     541                 :             : 
     542                 :       10187 :     tp_bitstream_reader *r = NULL;
     543                 :       10187 :     tp_result rc = tp_bs_reader_create(&r, it->dict->buf, (uint64_t)it->dict->len * 8);
     544                 :       10187 :     if (rc != TP_OK) /* LCOV_EXCL_BR_LINE */
     545                 :             :         return rc;   /* LCOV_EXCL_LINE */
     546                 :             : 
     547                 :             :     uint64_t subtree_end;
     548                 :             :     bool descending;
     549                 :             : 
     550         [ +  + ]:       10187 :     if (!it->started) {
     551                 :        2583 :         it->started = true;
     552                 :        2583 :         it->pos = it->root_pos;
     553                 :        2583 :         it->key_len = it->root_key_len;
     554                 :        2583 :         subtree_end = it->root_end;
     555                 :        2583 :         descending = true;
     556                 :             :     } else {
     557                 :        7604 :         subtree_end = 0;
     558                 :        7604 :         descending = false;
     559                 :             :     }
     560                 :             : 
     561                 :             : #define ITER_FAIL(code)           \
     562                 :             :     do {                          \
     563                 :             :         tp_bs_reader_destroy(&r); \
     564                 :             :         it->done = true;          \
     565                 :             :         return (code);            \
     566                 :             :     } while (0)
     567                 :             : 
     568                 :        3186 :     for (;;) {
     569         [ +  + ]:       13373 :         if (!descending) {
     570                 :             :             /* Move to the next unvisited sibling, unwinding finished frames. */
     571   [ +  +  +  + ]:       14660 :             while (it->stack_top >= 0 && it->stack[it->stack_top].remaining == 0)
     572                 :        3870 :                 it->stack_top--;
     573         [ +  + ]:       10790 :             if (it->stack_top < 0) {
     574                 :        1947 :                 tp_bs_reader_destroy(&r);
     575                 :        1947 :                 it->done = true;
     576                 :        1947 :                 return TP_ERR_EOF;
     577                 :             :             }
     578                 :             : 
     579                 :        8843 :             tp_iter_frame *f = &it->stack[it->stack_top];
     580                 :        8843 :             it->key_len = f->key_prefix_len;
     581                 :        8843 :             rc = tp_bs_reader_seek(r, f->next_child_pos);
     582                 :        8843 :             if (rc != TP_OK)   /* LCOV_EXCL_BR_LINE */
     583                 :             :                 ITER_FAIL(rc); /* LCOV_EXCL_LINE */
     584                 :             : 
     585         [ +  + ]:        8843 :             if (f->remaining > 1) {
     586                 :             :                 /* Every child but the last is introduced by SKIP + distance,
     587                 :             :                    which is also where the following sibling begins. */
     588                 :             :                 uint64_t skip_sym;
     589                 :        4399 :                 rc = tp_bs_read_bits(r, bps, &skip_sym);
     590         [ -  + ]:        4399 :                 if (rc != TP_OK)
     591                 :         120 :                     ITER_FAIL(rc);
     592         [ +  + ]:        4399 :                 if ((uint32_t)skip_sym != sym->ctrl_codes[TP_CTRL_SKIP])
     593                 :         119 :                     ITER_FAIL(TP_ERR_CORRUPT);
     594                 :             :                 uint64_t dist;
     595                 :        4280 :                 rc = tp_bs_read_varint_u(r, &dist);
     596         [ +  + ]:        4280 :                 if (rc != TP_OK)
     597                 :           1 :                     ITER_FAIL(rc);
     598                 :        4279 :                 it->pos = tp_bs_reader_position(r);
     599                 :        4279 :                 subtree_end = it->pos + dist;
     600                 :        4279 :                 f->next_child_pos = it->pos + dist;
     601                 :             :             } else {
     602                 :        4444 :                 it->pos = f->next_child_pos;
     603                 :        4444 :                 subtree_end = f->subtree_end;
     604                 :             :             }
     605                 :        8723 :             f->remaining--;
     606                 :        8723 :             descending = true;
     607                 :             :         }
     608                 :             : 
     609                 :       11306 :         rc = tp_bs_reader_seek(r, it->pos);
     610         [ +  + ]:       11306 :         if (rc != TP_OK)
     611                 :             :             ITER_FAIL(rc); /* LCOV_EXCL_LINE */
     612                 :             : 
     613                 :             :         /* Walk down this subtree until it yields a key or a branch. */
     614                 :       11283 :         bool emitted = false;
     615         [ +  + ]:       39346 :         while (tp_bs_reader_position(r) < subtree_end) {
     616                 :             :             uint64_t raw;
     617                 :       39327 :             rc = tp_bs_read_bits(r, bps, &raw);
     618         [ +  + ]:       39327 :             if (rc != TP_OK)
     619                 :         489 :                 ITER_FAIL(rc);
     620                 :       39326 :             uint32_t code = (uint32_t)raw;
     621                 :             : 
     622                 :       39326 :             bool terminal =
     623   [ +  +  +  + ]:       39326 :                 (code == sym->ctrl_codes[TP_CTRL_END] || code == sym->ctrl_codes[TP_CTRL_END_VAL]);
     624                 :             : 
     625         [ +  + ]:       39326 :             if (terminal) {
     626                 :             :                 /* END carries no index because its value is null; END_VAL
     627                 :             :                    names the slot in the value store. */
     628                 :        8012 :                 bool has_index = (code == sym->ctrl_codes[TP_CTRL_END_VAL]);
     629                 :        8012 :                 uint64_t vi = 0;
     630         [ +  + ]:        8012 :                 if (has_index) {
     631                 :        7909 :                     rc = tp_bs_read_varint_u(r, &vi);
     632         [ -  + ]:        7909 :                     if (rc != TP_OK)
     633                 :         404 :                         ITER_FAIL(rc);
     634                 :             :                 }
     635                 :             : 
     636                 :             :                 /* A BRANCH follows exactly when the subtree continues. */
     637         [ +  + ]:        8012 :                 if (tp_bs_reader_position(r) < subtree_end) {
     638                 :             :                     uint64_t bsym;
     639                 :        2001 :                     rc = tp_bs_read_bits(r, bps, &bsym);
     640         [ -  + ]:        2001 :                     if (rc != TP_OK)
     641                 :         179 :                         ITER_FAIL(rc);
     642         [ +  + ]:        2001 :                     if ((uint32_t)bsym != sym->ctrl_codes[TP_CTRL_BRANCH])
     643                 :         178 :                         ITER_FAIL(TP_ERR_CORRUPT);
     644                 :             :                     uint64_t nchildren;
     645                 :        1823 :                     rc = tp_bs_read_varint_u(r, &nchildren);
     646         [ -  + ]:        1823 :                     if (rc != TP_OK)
     647                 :           0 :                         ITER_FAIL(rc);
     648         [ +  + ]:        1823 :                     if (it->stack_top + 1 >= TP_ITER_MAX_DEPTH)
     649                 :           1 :                         ITER_FAIL(TP_ERR_OVERFLOW);
     650                 :        1822 :                     tp_iter_frame *nf = &it->stack[++it->stack_top];
     651                 :        1822 :                     nf->subtree_end = subtree_end;
     652                 :        1822 :                     nf->next_child_pos = tp_bs_reader_position(r);
     653                 :        1822 :                     nf->remaining = (uint32_t)nchildren;
     654                 :        1822 :                     nf->key_prefix_len = it->key_len;
     655                 :             :                 }
     656                 :             : 
     657                 :        7833 :                 tp_value decoded = tp_value_null();
     658         [ +  + ]:        7833 :                 if (has_index) {
     659                 :        7825 :                     rc = iter_value_at(it, vi, &decoded);
     660         [ +  + ]:        7825 :                     if (rc != TP_OK)
     661                 :         225 :                         ITER_FAIL(rc);
     662                 :             :                 }
     663         [ +  - ]:        7608 :                 if (val)
     664                 :        7608 :                     *val = decoded;
     665         [ +  - ]:        7608 :                 if (key)
     666                 :        7608 :                     *key = it->key_buf;
     667         [ +  - ]:        7608 :                 if (key_len)
     668                 :        7608 :                     *key_len = it->key_len;
     669                 :        7608 :                 emitted = true;
     670                 :        7608 :                 break;
     671                 :             :             }
     672                 :             : 
     673         [ +  + ]:       31314 :             if (code == sym->ctrl_codes[TP_CTRL_BRANCH]) {
     674                 :             :                 uint64_t nchildren;
     675                 :        3167 :                 rc = tp_bs_read_varint_u(r, &nchildren);
     676         [ -  + ]:        3167 :                 if (rc != TP_OK)
     677                 :           0 :                     ITER_FAIL(rc);
     678         [ -  + ]:        3167 :                 if (it->stack_top + 1 >= TP_ITER_MAX_DEPTH)
     679                 :           0 :                     ITER_FAIL(TP_ERR_OVERFLOW);
     680                 :        3167 :                 tp_iter_frame *nf = &it->stack[++it->stack_top];
     681                 :        3167 :                 nf->subtree_end = subtree_end;
     682                 :        3167 :                 nf->next_child_pos = tp_bs_reader_position(r);
     683                 :        3167 :                 nf->remaining = (uint32_t)nchildren;
     684                 :        3167 :                 nf->key_prefix_len = it->key_len;
     685                 :        3167 :                 descending = false;
     686                 :        3167 :                 break;
     687                 :             :             }
     688                 :             : 
     689                 :             :             /* Regular symbol: extends the key. */
     690   [ +  -  +  + ]:       28147 :             if (code < 256 && sym->code_is_ctrl[code])
     691                 :          84 :                 ITER_FAIL(TP_ERR_CORRUPT);
     692         [ +  - ]:       28063 :             rc = iter_push_key(it, code < 256 ? sym->reverse_map[code] : 0);
     693                 :       28063 :             if (rc != TP_OK)   /* LCOV_EXCL_BR_LINE */
     694                 :             :                 ITER_FAIL(rc); /* LCOV_EXCL_LINE */
     695                 :             :         }
     696                 :             : 
     697         [ +  + ]:       10794 :         if (emitted) {
     698                 :        7608 :             tp_bs_reader_destroy(&r);
     699                 :        7608 :             return TP_OK;
     700                 :             :         }
     701         [ +  + ]:        3186 :         if (descending) {
     702                 :             :             /* Ran to the subtree end without a terminal: try the next sibling. */
     703                 :          19 :             descending = false;
     704                 :             :         }
     705                 :             :     }
     706                 :             : 
     707                 :             : #undef ITER_FAIL
     708                 :             : }
     709                 :             : 
     710                 :        6243 : tp_result tp_iter_reset(tp_iterator *it)
     711                 :             : {
     712         [ +  + ]:        6243 :     if (!it)
     713                 :           1 :         return TP_ERR_INVALID_PARAM;
     714                 :        6242 :     it->pos = it->root_pos;
     715                 :        6242 :     it->done = false;
     716                 :        6242 :     it->key_len = it->root_key_len;
     717                 :        6242 :     it->stack_top = -1;
     718                 :        6242 :     it->started = false;
     719                 :        6242 :     it->value_pos = it->dict->value_start;
     720                 :        6242 :     it->value_index = 0;
     721                 :        6242 :     return TP_OK;
     722                 :             : }
     723                 :             : 
     724                 :        4414 : tp_result tp_iter_destroy(tp_iterator **it)
     725                 :             : {
     726         [ +  + ]:        4414 :     if (!it)
     727                 :           1 :         return TP_ERR_INVALID_PARAM;
     728         [ +  + ]:        4413 :     if (*it) {
     729                 :        4410 :         free((*it)->key_buf);
     730                 :        4410 :         free(*it);
     731                 :        4410 :         *it = NULL;
     732                 :             :     }
     733                 :        4413 :     return TP_OK;
     734                 :             : }
     735                 :             : 
     736                 :             : /* ── Search ─────────────────────────────────────────────────────────── */
     737                 :             : 
     738                 :             : /**
     739                 :             :  * Walk down the trie consuming `prefix`, and report the subtree that holds
     740                 :             :  * every key starting with it.
     741                 :             :  *
     742                 :             :  * On success *sub_pos / *sub_end bound that subtree. TP_ERR_NOT_FOUND means no
     743                 :             :  * key has the prefix.
     744                 :             :  */
     745                 :        3655 : static tp_result descend_to_prefix(const tp_dict *dict, const uint8_t *prefix, size_t prefix_len,
     746                 :             :                                    uint64_t *sub_pos, uint64_t *sub_end)
     747                 :             : {
     748                 :        3655 :     const tp_symbol_info *sym = &dict->sym;
     749                 :        3655 :     uint8_t bps = sym->bits_per_symbol;
     750                 :             : 
     751                 :        3655 :     tp_bitstream_reader *r = NULL;
     752                 :        3655 :     tp_result rc = tp_bs_reader_create(&r, dict->buf, (uint64_t)dict->len * 8);
     753                 :        3655 :     if (rc != TP_OK) /* LCOV_EXCL_BR_LINE */
     754                 :             :         return rc;   /* LCOV_EXCL_LINE */
     755                 :             : 
     756                 :        3655 :     uint64_t pos = dict->trie_start;
     757                 :        3655 :     uint64_t end = dict->value_start;
     758                 :        3655 :     size_t matched = 0;
     759                 :             : 
     760                 :             : #define PREFIX_FAIL(code)         \
     761                 :             :     do {                          \
     762                 :             :         tp_bs_reader_destroy(&r); \
     763                 :             :         return (code);            \
     764                 :             :     } while (0)
     765                 :             : 
     766                 :       13886 :     for (;;) {
     767         [ +  + ]:       17541 :         if (matched == prefix_len) {
     768                 :        1828 :             *sub_pos = pos;
     769                 :        1828 :             *sub_end = end;
     770                 :        1828 :             tp_bs_reader_destroy(&r);
     771                 :        3655 :             return TP_OK;
     772                 :             :         }
     773         [ +  + ]:       15713 :         if (pos >= end)
     774                 :         123 :             PREFIX_FAIL(TP_ERR_NOT_FOUND);
     775                 :             : 
     776                 :       15590 :         rc = tp_bs_reader_seek(r, pos);
     777                 :       15590 :         if (rc != TP_OK)     /* LCOV_EXCL_BR_LINE */
     778                 :             :             PREFIX_FAIL(rc); /* LCOV_EXCL_LINE */
     779                 :             : 
     780                 :             :         uint64_t raw;
     781                 :       15590 :         rc = tp_bs_read_bits(r, bps, &raw);
     782         [ -  + ]:       15590 :         if (rc != TP_OK)
     783                 :           0 :             PREFIX_FAIL(rc);
     784                 :       15590 :         uint32_t code = (uint32_t)raw;
     785                 :             : 
     786                 :       15590 :         bool at_branch = false;
     787   [ +  +  +  + ]:       15590 :         if (code == sym->ctrl_codes[TP_CTRL_END] || code == sym->ctrl_codes[TP_CTRL_END_VAL]) {
     788         [ +  + ]:        1090 :             if (code == sym->ctrl_codes[TP_CTRL_END_VAL]) {
     789                 :             :                 uint64_t vi;
     790                 :        1079 :                 rc = tp_bs_read_varint_u(r, &vi);
     791         [ -  + ]:        1079 :                 if (rc != TP_OK)
     792                 :           0 :                     PREFIX_FAIL(rc);
     793                 :             :             }
     794                 :             :             /* The prefix is longer than this key, so it can only continue
     795                 :             :                through the BRANCH that follows — if the subtree continues. */
     796         [ +  + ]:        1090 :             if (tp_bs_reader_position(r) >= end)
     797                 :         488 :                 PREFIX_FAIL(TP_ERR_NOT_FOUND);
     798                 :         602 :             rc = tp_bs_read_bits(r, bps, &raw);
     799         [ -  + ]:         602 :             if (rc != TP_OK)
     800                 :           0 :                 PREFIX_FAIL(rc);
     801         [ +  + ]:         602 :             if ((uint32_t)raw != sym->ctrl_codes[TP_CTRL_BRANCH])
     802                 :          56 :                 PREFIX_FAIL(TP_ERR_CORRUPT);
     803                 :         546 :             at_branch = true;
     804         [ +  + ]:       14500 :         } else if (code == sym->ctrl_codes[TP_CTRL_BRANCH]) {
     805                 :        4628 :             at_branch = true;
     806                 :             :         }
     807                 :             : 
     808         [ +  + ]:       15046 :         if (!at_branch) {
     809                 :             :             /* Literal symbol: it has to be the prefix byte we are looking for. */
     810   [ +  -  +  + ]:        9872 :             if (code < 256 && sym->code_is_ctrl[code])
     811                 :          21 :                 PREFIX_FAIL(TP_ERR_CORRUPT);
     812         [ +  - ]:        9851 :             uint8_t byte = code < 256 ? sym->reverse_map[code] : 0;
     813         [ +  + ]:        9851 :             if (byte != prefix[matched])
     814                 :         186 :                 PREFIX_FAIL(TP_ERR_NOT_FOUND);
     815                 :        9665 :             matched++;
     816                 :        9665 :             pos = tp_bs_reader_position(r);
     817                 :        9665 :             continue;
     818                 :             :         }
     819                 :             : 
     820                 :             :         /* At a BRANCH: take the child whose first symbol is the prefix byte. */
     821                 :             :         uint64_t nchildren;
     822                 :        5174 :         rc = tp_bs_read_varint_u(r, &nchildren);
     823         [ -  + ]:        5174 :         if (rc != TP_OK)
     824                 :           0 :             PREFIX_FAIL(rc);
     825                 :             : 
     826                 :        5174 :         uint64_t child_preamble = tp_bs_reader_position(r);
     827                 :        5174 :         uint32_t want = sym->symbol_map[prefix[matched]];
     828                 :        5174 :         bool descended = false;
     829                 :             : 
     830         [ +  + ]:        9859 :         for (uint64_t c = 0; c < nchildren; c++) {
     831                 :        9050 :             rc = tp_bs_reader_seek(r, child_preamble);
     832         [ +  + ]:        9050 :             if (rc != TP_OK)
     833                 :             :                 PREFIX_FAIL(rc); /* LCOV_EXCL_LINE */
     834                 :             : 
     835                 :             :             uint64_t child_start, child_end;
     836         [ +  + ]:        9021 :             if (c + 1 < nchildren) {
     837                 :             :                 uint64_t skip_sym;
     838                 :        4668 :                 rc = tp_bs_read_bits(r, bps, &skip_sym);
     839         [ -  + ]:        4668 :                 if (rc != TP_OK)
     840                 :         115 :                     PREFIX_FAIL(rc);
     841         [ +  + ]:        4668 :                 if ((uint32_t)skip_sym != sym->ctrl_codes[TP_CTRL_SKIP])
     842                 :         115 :                     PREFIX_FAIL(TP_ERR_CORRUPT);
     843                 :             :                 uint64_t dist;
     844                 :        4553 :                 rc = tp_bs_read_varint_u(r, &dist);
     845         [ -  + ]:        4553 :                 if (rc != TP_OK)
     846                 :           0 :                     PREFIX_FAIL(rc);
     847                 :        4553 :                 child_start = tp_bs_reader_position(r);
     848                 :        4553 :                 child_end = child_start + dist;
     849                 :        4553 :                 child_preamble = child_start + dist;
     850                 :             :             } else {
     851                 :        4353 :                 child_start = child_preamble;
     852                 :        4353 :                 child_end = end;
     853                 :             :             }
     854                 :             : 
     855                 :             :             uint64_t first;
     856                 :        8906 :             rc = tp_bs_read_bits_at(dict->buf, child_start, bps, &first);
     857                 :        8906 :             if (rc != TP_OK)     /* LCOV_EXCL_BR_LINE */
     858                 :             :                 PREFIX_FAIL(rc); /* LCOV_EXCL_LINE */
     859                 :             : 
     860         [ +  + ]:        8906 :             if ((uint32_t)first == want) {
     861                 :        4221 :                 pos = child_start;
     862                 :        4221 :                 end = child_end;
     863                 :        4221 :                 descended = true;
     864                 :        4221 :                 break;
     865                 :             :             }
     866                 :             :         }
     867                 :             : 
     868         [ +  + ]:        5030 :         if (!descended)
     869                 :         809 :             PREFIX_FAIL(TP_ERR_NOT_FOUND);
     870                 :             :     }
     871                 :             : 
     872                 :             : #undef PREFIX_FAIL
     873                 :             : }
     874                 :             : 
     875                 :        3657 : tp_result tp_dict_find_prefix(const tp_dict *dict, const char *prefix, tp_iterator **out)
     876                 :             : {
     877   [ +  +  +  -  :        3657 :     if (!dict || !prefix || !out)
                   -  + ]
     878                 :           1 :         return TP_ERR_INVALID_PARAM;
     879                 :             : 
     880                 :        3656 :     tp_iterator *it = NULL;
     881                 :        3656 :     tp_result rc = tp_dict_iterate(dict, &it);
     882                 :        3656 :     if (rc != TP_OK) /* LCOV_EXCL_BR_LINE */
     883                 :             :         return rc;   /* LCOV_EXCL_LINE */
     884                 :             : 
     885                 :        3656 :     size_t prefix_len = strlen(prefix);
     886   [ +  +  -  + ]:        3656 :     if (prefix_len == 0 || dict->info.num_keys == 0) {
     887                 :           1 :         *out = it;
     888                 :           1 :         return TP_OK;
     889                 :             :     }
     890                 :             : 
     891                 :        3655 :     uint64_t sub_pos = 0, sub_end = 0;
     892                 :        3655 :     rc = descend_to_prefix(dict, (const uint8_t *)prefix, prefix_len, &sub_pos, &sub_end);
     893         [ +  + ]:        3655 :     if (rc == TP_ERR_NOT_FOUND) {
     894                 :             :         /* No key has this prefix: hand back an iterator that yields nothing. */
     895                 :        1606 :         it->done = true;
     896                 :        1606 :         *out = it;
     897                 :        1606 :         return TP_OK;
     898                 :             :     }
     899         [ +  + ]:        2049 :     if (rc != TP_OK) {
     900                 :         221 :         tp_iter_destroy(&it);
     901                 :         221 :         return rc;
     902                 :             :     }
     903                 :             : 
     904                 :             :     /* Seed the iterator with the prefix already in place, then let it walk
     905                 :             :        only the subtree below it. */
     906                 :        1828 :     if (iter_key_reserve(it, prefix_len) != TP_OK) { /* LCOV_EXCL_BR_LINE */
     907                 :             :         /* LCOV_EXCL_START */
     908                 :             :         tp_iter_destroy(&it);
     909                 :             :         return TP_ERR_ALLOC;
     910                 :             :         /* LCOV_EXCL_STOP */
     911                 :             :     }
     912                 :        1828 :     memcpy(it->key_buf, prefix, prefix_len);
     913                 :        1828 :     it->root_pos = sub_pos;
     914                 :        1828 :     it->root_end = sub_end;
     915                 :        1828 :     it->root_key_len = prefix_len;
     916                 :             : 
     917                 :        1828 :     tp_iter_reset(it);
     918                 :        1828 :     *out = it;
     919                 :        1828 :     return TP_OK;
     920                 :             : }
     921                 :             : 
     922                 :           2 : tp_result tp_dict_find_fuzzy(const tp_dict *dict, const char *query, uint8_t max_dist,
     923                 :             :                              tp_iterator **out)
     924                 :             : {
     925   [ +  +  +  -  :           2 :     if (!dict || !query || !out)
                   -  + ]
     926                 :           1 :         return TP_ERR_INVALID_PARAM;
     927                 :             :     /* Bounded edit-distance search over the trie is not implemented. Saying so
     928                 :             :        beats handing back an iterator over every key, which — now that
     929                 :             :        iteration works — would look like a successful fuzzy match for
     930                 :             :        anything. */
     931                 :             :     (void)max_dist;
     932                 :           1 :     *out = NULL;
     933                 :           1 :     return TP_ERR_UNSUPPORTED;
     934                 :             : }
     935                 :             : 
     936                 :           3 : tp_result tp_iter_get_distance(const tp_iterator *it, uint8_t *dist)
     937                 :             : {
     938   [ +  +  -  + ]:           3 :     if (!it || !dist)
     939                 :           2 :         return TP_ERR_INVALID_PARAM;
     940                 :           1 :     *dist = it->distance;
     941                 :           1 :     return TP_OK;
     942                 :             : }
        

Generated by: LCOV version 2.0-1