|
26 | 26 |
|
27 | 27 | #include "character_map_embedded.h" |
28 | 28 | #include "printable_binary.h" |
| 29 | +#include "container_json.h" |
29 | 30 |
|
30 | 31 | #ifdef __EMSCRIPTEN__ |
31 | 32 | // Standalone WASM builds shouldn't depend on host-provided env functions. |
@@ -126,6 +127,7 @@ typedef struct { |
126 | 127 | int64_t range_end; |
127 | 128 | bool no_double_encode_check; |
128 | 129 | bool hexlike_mode; |
| 130 | + bool container_mode; |
129 | 131 | } options_t; |
130 | 132 |
|
131 | 133 | static void parse_format_spec(options_t *opts, const char *format_str) { |
@@ -801,6 +803,21 @@ pb_double_encode_info_t pb_detect_double_encode(const char *input, size_t input_ |
801 | 803 | return detect_double_encode(input, input_len, threshold); |
802 | 804 | } |
803 | 805 |
|
| 806 | +// CRC-32/ISO-HDLC (vector-pinned: CRC32("123456789")=0xCBF43926). Self-contained |
| 807 | +// (the standalone build does not link the Zig lib). Backs .pbf.json container |
| 808 | +// integrity; bitwise, ample for container-sized payloads. |
| 809 | +uint32_t pb_crc32(const char *input, size_t input_len) { |
| 810 | + if (!input || input_len == 0) return 0u; |
| 811 | + uint32_t crc = 0xFFFFFFFFu; |
| 812 | + for (size_t i = 0; i < input_len; i++) { |
| 813 | + crc ^= (unsigned char)input[i]; |
| 814 | + for (int k = 0; k < 8; k++) { |
| 815 | + crc = (crc & 1u) ? ((crc >> 1) ^ 0xEDB88320u) : (crc >> 1); |
| 816 | + } |
| 817 | + } |
| 818 | + return ~crc; |
| 819 | +} |
| 820 | + |
804 | 821 | // Hexlike passthrough set: bytes that pass through as-is in hexlike mode |
805 | 822 | // . 0-9 @ A-Z ^ _ a-z (the same bytes that are identity-mapped in the character map) |
806 | 823 | static bool is_hexlike_passthrough(uint8_t byte, bool spaces_mode) { |
@@ -1242,6 +1259,8 @@ static void print_usage(const char *program_name) { |
1242 | 1259 | fprintf(stderr, " -p, --passthrough Pass input to stdout unchanged, send encoded data to stderr\n"); |
1243 | 1260 | fprintf(stderr, "\nEncoding modes:\n"); |
1244 | 1261 | fprintf(stderr, " -X, --hexlike Hexlike mode: passthrough ASCII stays as-is, all other bytes\n"); |
| 1262 | + fprintf(stderr, " -C, --container Container mode: encode a file to a self-verifying .pbf.json\n"); |
| 1263 | + fprintf(stderr, " (keeps filename + crc32). With -d, decode a container back.\n"); |
1245 | 1264 | fprintf(stderr, " shown as uppercase hex runs prefixed by \xCE\x9F\xCF\x87 (Greek Omicron+Chi,\n"); |
1246 | 1265 | fprintf(stderr, " NOT ASCII 0x \xe2\x80\x94 beware when copying hex for other purposes).\n"); |
1247 | 1266 | fprintf(stderr, " Use with -d to decode hexlike-encoded data back to binary.\n"); |
@@ -1533,6 +1552,8 @@ static options_t parse_options(int argc, char *argv[]) { |
1533 | 1552 | opts.no_double_encode_check = true; |
1534 | 1553 | } else if (long_option_equals(name, name_len, "hexlike")) { |
1535 | 1554 | opts.hexlike_mode = true; |
| 1555 | + } else if (long_option_equals(name, name_len, "container")) { |
| 1556 | + opts.container_mode = true; |
1536 | 1557 | } else if (long_option_equals(name, name_len, "help")) { |
1537 | 1558 | opts.help_mode = true; |
1538 | 1559 | } else { |
@@ -1580,6 +1601,10 @@ static options_t parse_options(int argc, char *argv[]) { |
1580 | 1601 | opts.hexlike_mode = true; |
1581 | 1602 | pos++; |
1582 | 1603 | break; |
| 1604 | + case 'C': |
| 1605 | + opts.container_mode = true; |
| 1606 | + pos++; |
| 1607 | + break; |
1583 | 1608 | case 'P': { |
1584 | 1609 | if (arg[pos + 1] != '\0') { |
1585 | 1610 | const char *value = &arg[pos + 1]; |
@@ -1751,6 +1776,57 @@ int main(int argc, char *argv[]) { |
1751 | 1776 | } |
1752 | 1777 | } |
1753 | 1778 |
|
| 1779 | + if (opts.container_mode) { |
| 1780 | + if (opts.decode_mode) { |
| 1781 | + size_t dlen; |
| 1782 | + const char *draw = cj_get_string(input.data, input.size, "data", &dlen); |
| 1783 | + if (!draw) { fprintf(stderr, "Error: not a printable-binary-file container (missing 'data')\n"); buffer_free(&input); return 1; } |
| 1784 | + size_t clen; |
| 1785 | + char *clean = cj_canonical(draw, dlen, &clen); |
| 1786 | + size_t celen; |
| 1787 | + const char *ce = cj_get_string(input.data, input.size, "crc32_encoded", &celen); |
| 1788 | + if (ce) { |
| 1789 | + char hx[9]; snprintf(hx, 9, "%08x", (unsigned int)pb_crc32(clean, clen)); |
| 1790 | + if (celen != 8 || memcmp(hx, ce, 8) != 0) { free(clean); buffer_free(&input); fprintf(stderr, "Error: container crc32_encoded mismatch (data corrupted)\n"); return 1; } |
| 1791 | + } |
| 1792 | + buffer_t dec = decode_data((uint8_t *)clean, clen, false); |
| 1793 | + free(clean); |
| 1794 | + size_t colen; |
| 1795 | + const char *co = cj_get_string(input.data, input.size, "crc32", &colen); |
| 1796 | + if (co) { |
| 1797 | + char hx[9]; snprintf(hx, 9, "%08x", (unsigned int)pb_crc32(dec.data, dec.size)); |
| 1798 | + if (colen != 8 || memcmp(hx, co, 8) != 0) { buffer_free(&dec); buffer_free(&input); fprintf(stderr, "Error: container crc32 mismatch (decoded data corrupted)\n"); return 1; } |
| 1799 | + } |
| 1800 | + fwrite(dec.data, 1, dec.size, stdout); |
| 1801 | + buffer_free(&dec); |
| 1802 | + buffer_free(&input); |
| 1803 | + return 0; |
| 1804 | + } else { |
| 1805 | + options_t enc_opts = opts; |
| 1806 | + enc_opts.spaces_mode = false; |
| 1807 | + enc_opts.tabs_mode = false; |
| 1808 | + enc_opts.crlf_mode = false; |
| 1809 | + enc_opts.preserve_chars[0] = '\0'; |
| 1810 | + buffer_t enc = encode_data((uint8_t *)input.data, input.size, &enc_opts); |
| 1811 | + size_t clen; |
| 1812 | + char *clean = cj_canonical(enc.data, enc.size, &clen); |
| 1813 | + char crc_orig[9], crc_enc[9]; |
| 1814 | + snprintf(crc_orig, 9, "%08x", (unsigned int)pb_crc32(input.data, input.size)); |
| 1815 | + snprintf(crc_enc, 9, "%08x", (unsigned int)pb_crc32(clean, clen)); |
| 1816 | + free(clean); |
| 1817 | + const char *fname = (opts.input_file && strcmp(opts.input_file, "-") != 0) ? cj_basename(opts.input_file) : ""; |
| 1818 | + printf("{\n \"format\": \"printable-binary-file\",\n \"version\": 1,\n \"filename\": \""); |
| 1819 | + cj_fputs_escaped(stdout, fname, strlen(fname)); |
| 1820 | + printf("\",\n \"byte_length\": %zu,\n \"crc32\": \"%s\",\n \"crc32_encoded\": \"%s\",\n \"data\": \"", input.size, crc_orig, crc_enc); |
| 1821 | + fwrite(enc.data, 1, enc.size, stdout); |
| 1822 | + printf("\"\n}\n"); |
| 1823 | + buffer_free(&enc); |
| 1824 | + buffer_free(&input); |
| 1825 | + return 0; |
| 1826 | + } |
| 1827 | + } |
| 1828 | + |
| 1829 | + |
1754 | 1830 | if (opts.decode_mode) { |
1755 | 1831 | if (opts.passthrough_mode) { |
1756 | 1832 | fprintf(stderr, "Warning: --passthrough ignored in decode mode\n"); |
|
0 commit comments