1#include "ggml.h"
  2#include "gguf.h"
  3
  4#include <cstdlib>   /* abort() */
  5#include <cstddef>
  6#include <cstdio>
  7#include <string>
  8#include <stdexcept>
  9#include <algorithm>
 10#include <cstring>
 11
 12#include <sstream>
 13#include <fstream>
 14
 15#ifdef __cplusplus
 16extern "C" {
 17#endif
 18
 19#include "xxhash/xxhash.h"
 20#include "sha1/sha1.h"
 21#include "sha256/sha256.h"
 22
 23#ifdef __cplusplus
 24}
 25#endif
 26
 27
 28// uuid.uuid5(uuid.NAMESPACE_URL, 'en.wikipedia.org/wiki/Llama.cpp')
 29#define UUID_NAMESPACE_LLAMA_CPP "ef001206-dadc-5f6d-a15f-3359e577d4e5"
 30#define UUID_NAMESPACE_LLAMA_CPP_HEX 0xef, 0x00, 0x12, 0x06, 0xda, 0xdc, 0x5f, 0x6d, 0xa1, 0x5f, 0x33, 0x59, 0xe5, 0x77, 0xd4, 0xe5
 31
 32
 33#define HASH_TYPE_SHA256_STR "sha256"
 34#define HASH_TYPE_SHA1_STR   "sha1"
 35#define HASH_TYPE_XXH64_STR  "xxh64"
 36#define HASH_TYPE_UUID_STR   "uuid"
 37
 38
 39typedef enum {
 40    HASH_EXIT_SUCCESS = 0, // All hash has been generated or validated
 41    HASH_EXIT_FAILURE = 1, // Generic Failure
 42    HASH_EXIT_MISMATCH = 2, // Hash mismatched during validation
 43    HASH_EXIT_MANIFEST_MISSING_ENTRY = 3, // Hash attempted validation but missing entry in manifest
 44    HASH_EXIT_MANIFEST_UNKNOWN_HASH = 4, // Manifest is present, but we do not know any hash format within it
 45    HASH_EXIT_MANIFEST_FILE_ERROR = 5 // Manifest is either missing or not a known format
 46} hash_exit_code_t;
 47
 48
 49typedef enum {
 50    HASH_MANIFEST_NOT_FOUND,
 51    HASH_MANIFEST_MISMATCH,
 52    HASH_MANIFEST_OK,
 53} hash_manifest_result_t;
 54
 55
 56struct hash_params {
 57    std::string input;
 58    bool xxh64 = false;
 59    bool sha1 = false;
 60    bool sha256 = false;
 61    bool uuid = false;
 62
 63    bool no_layer = false;
 64
 65    bool manifest_is_usable = false;
 66    std::string manifest_file;
 67};
 68
 69struct manifest_check_params {
 70    bool xxh64 = false;
 71    bool sha1 = false;
 72    bool sha256 = false;
 73    bool uuid = false;
 74};
 75
 76static char const * hash_manifest_result_to_str(hash_manifest_result_t value) {
 77    switch (value) {
 78        case HASH_MANIFEST_NOT_FOUND: return "Not Found";
 79        case HASH_MANIFEST_MISMATCH: return "Mismatch";
 80        case HASH_MANIFEST_OK: return "Ok";
 81    }
 82    return "?";
 83}
 84
 85static char const * hash_exit_code_to_str(hash_exit_code_t value) {
 86    switch (value) {
 87        case HASH_EXIT_SUCCESS: return "Success";
 88        case HASH_EXIT_FAILURE: return "Failure";
 89        case HASH_EXIT_MISMATCH: return "Mismatch";
 90        case HASH_EXIT_MANIFEST_MISSING_ENTRY: return "Manifest Missing Entry";
 91        case HASH_EXIT_MANIFEST_UNKNOWN_HASH: return "Manifest Unknown Hash";
 92        case HASH_EXIT_MANIFEST_FILE_ERROR: return "Manifest File Error";
 93    }
 94    return "?";
 95}
 96
 97static void hash_print_usage(const char * executable) {
 98    const hash_params default_params;
 99    printf("\n");
100    printf("usage: %s [options] GGUF_IN\n", executable);
101    printf("\n");
102    printf("Hash a GGUF file");
103    printf("\n");
104    printf("options:\n");
105    printf("  -h, --help              show this help message and exit\n");
106    printf("      --xxh64             use xxh64 hash\n");
107    printf("      --sha1              use sha1 hash\n");
108    printf("      --sha256            use sha256 hash\n");
109    printf("      --all               use all hash\n");
110    printf("      --no-layer          exclude per layer hash\n");
111    printf("      --uuid              generate UUIDv5 ID\n");
112    printf("  -c, --check <manifest>  verify against a manifest\n");
113    printf("\n");
114}
115
116static void hash_params_parse_ex(int argc, const char ** argv, hash_params & params) {
117    std::string arg;
118    bool invalid_param = false;
119    const std::string arg_prefix = "--";
120
121    int arg_idx = 1;
122    for (; arg_idx < argc && strncmp(argv[arg_idx], "--", 2) == 0; arg_idx++) {
123        arg = argv[arg_idx];
124        if (arg.compare(0, arg_prefix.size(), arg_prefix) == 0) {
125            std::replace(arg.begin(), arg.end(), '_', '-');
126        }
127
128        bool arg_found = false;
129        if (arg == "-h" || arg == "--help") {
130            hash_print_usage(argv[0]);
131            exit(0);
132        }
133
134        if (arg == "--xxh64") {
135            arg_found = true;
136            params.xxh64 = true;
137        }
138
139        if (arg == "--sha1") {
140            arg_found = true;
141            params.sha1 = true;
142        }
143
144        if (arg == "--uuid") {
145            arg_found = true;
146            params.uuid = true;
147        }
148
149        if (arg == "--sha256") {
150            arg_found = true;
151            params.sha256 = true;
152        }
153
154        if (arg == "--all") {
155            arg_found = true;
156            params.sha256 = true;
157            params.sha1 = true;
158            params.xxh64 = true;
159        }
160
161        if (arg == "--no-layer") {
162            arg_found = true;
163            params.no_layer = true;
164        }
165
166        if (arg == "-c" || arg == "--check") {
167            if (++arg_idx >= argc) {
168                invalid_param = true;
169                break;
170            }
171            arg_found = true;
172            params.manifest_file = argv[arg_idx];
173        }
174
175        if (!arg_found) {
176            throw std::invalid_argument("error: unknown argument: " + arg);
177        }
178    }
179
180    if (invalid_param) {
181        throw std::invalid_argument("error: invalid parameter for argument:" + arg);
182    }
183
184    if (argc - arg_idx < 1) {
185        throw std::invalid_argument("error: bad arguments");
186    }
187
188    params.input = argv[arg_idx++];
189}
190
191static bool hash_params_parse(int argc, const char ** argv, hash_params & params) {
192    bool result = true;
193    try {
194        hash_params_parse_ex(argc, argv, params);
195    }
196    catch (const std::invalid_argument & ex) {
197        fprintf(stderr, "%s\n", ex.what());
198        hash_print_usage(argv[0]);
199        exit(EXIT_FAILURE);
200    }
201    return result;
202}
203
204static bool manifest_type(const std::string & manifest_file, manifest_check_params & manifest_check) {
205    if (manifest_file.empty()) {
206        return false;
207    }
208
209    std::ifstream file(manifest_file);
210    if (!file.is_open()) {
211        return false;
212    }
213
214    std::string manifest_entry_line;
215    while (getline(file, manifest_entry_line)) {
216        // hash_type_str hash_str tensor_name
217        // e.g. 'xxh64     f66e9cd66a4396a0  test.gguf:tensor_0'
218        std::istringstream line_stream(manifest_entry_line);
219        std::string file_hash_type;
220        if (line_stream >> file_hash_type) {
221            if (file_hash_type == HASH_TYPE_SHA256_STR) {
222                manifest_check.sha256 = true;
223            } else if (file_hash_type == HASH_TYPE_SHA1_STR) {
224                manifest_check.sha1 = true;
225            } else if (file_hash_type == HASH_TYPE_XXH64_STR) {
226                manifest_check.xxh64 = true;
227            } else if (file_hash_type == HASH_TYPE_UUID_STR) {
228                manifest_check.uuid = true;
229            }
230        }
231    }
232
233    return true;
234}
235
236static hash_manifest_result_t manifest_verify(const std::string& manifest_file, const std::string& hash_type_str, const std::string& hash_str, const std::string& tensor_name) {
237    if (manifest_file.empty()) {
238        return HASH_MANIFEST_NOT_FOUND;
239    }
240
241    std::ifstream file(manifest_file);
242    if (!file.is_open()) {
243        return HASH_MANIFEST_NOT_FOUND;
244    }
245
246    std::string manifest_entry_line;
247    while (getline(file, manifest_entry_line)) {
248        std::istringstream line_stream(manifest_entry_line);
249        std::string file_hash_type;
250        std::string file_hash;
251        std::string file_tensor_name;
252        if (line_stream >> file_hash_type >> file_hash >> file_tensor_name) {
253            // Line parsed. Check hash validity
254
255            if (file_hash_type != hash_type_str) {
256                continue;
257            }
258
259            if (file_tensor_name != tensor_name) {
260                continue;
261            }
262
263            return (file_hash == hash_str) ? HASH_MANIFEST_OK : HASH_MANIFEST_MISMATCH;
264        }
265    }
266
267    return HASH_MANIFEST_NOT_FOUND;
268}
269
270static void generate_uuidv5(const unsigned char sha1_digest[20], unsigned char uuid[16]) {
271    // Ref: https://www.rfc-editor.org/rfc/rfc9562.html#section-5.5
272    // Assumes that digest was processed correctly with the expected namespace
273    for (int i = 0; i < 16; i++) {
274        uuid[i] = sha1_digest[i];
275    }
276
277    // Set bits corresponding to UUID ver 5
278    uuid[ 6] &= ~(0xF << 4);
279    uuid[ 6] |= (5 << 4);
280
281    // Set bits corresponding to UUID variant 0b10XX
282    uuid[ 8] &= ~(0xc << 4);
283    uuid[ 8] |= (0x8 << 4);
284}
285
286static hash_exit_code_t gguf_hash(const hash_params & hash_params) {
287    const std::string & fname = hash_params.input;
288    struct ggml_context * ctx_data = NULL;
289
290    struct gguf_init_params params = {
291        /*.no_alloc = */ false,
292        /*.ctx      = */ &ctx_data,
293    };
294
295    // xxh64 init
296    XXH64_state_t* xxh64_model_hash_state = NULL;
297    if (hash_params.xxh64) {
298        xxh64_model_hash_state = XXH64_createState();
299        if (xxh64_model_hash_state==NULL) {
300            abort();
301        }
302
303        XXH64_hash_t const seed = 0;
304        if (XXH64_reset(xxh64_model_hash_state, seed) == XXH_ERROR) {
305            abort();
306        }
307    }
308
309    // sha1 init
310    SHA1_CTX sha1_model_hash_ctx;
311    if (hash_params.sha1) {
312        SHA1Init(&sha1_model_hash_ctx);
313    }
314
315    // sha256 init
316    sha256_t sha256_model_hash_ctx;
317    if (hash_params.sha256) {
318        sha256_init(&sha256_model_hash_ctx);
319    }
320
321    // sha1 for uuid init
322    SHA1_CTX sha1_for_uuid_ctx;
323    if (hash_params.uuid) {
324        unsigned char const uuidv5_namespace[] = {UUID_NAMESPACE_LLAMA_CPP_HEX};
325        SHA1Init(&sha1_for_uuid_ctx);
326        SHA1Update( &sha1_for_uuid_ctx, (unsigned char const *)uuidv5_namespace, sizeof(uuidv5_namespace));
327    }
328
329    struct gguf_context * ctx = gguf_init_from_file(fname.c_str(), params);
330    const int n_tensors = gguf_get_n_tensors(ctx);
331    bool tensor_layer_in_manifest = false;
332    bool model_in_manifest = false;
333    bool tensor_layer_has_mismatch = false;
334    bool model_has_mismatch = false;
335    for (int i = 0; i < n_tensors; ++i) {
336        const char * name = gguf_get_tensor_name(ctx, i);
337        struct ggml_tensor * cur = ggml_get_tensor(ctx_data, name);
338        auto n_bytes = ggml_nbytes(cur);
339        auto *raw_data = cur->data;
340        const std::string tensor_layer_name = fname + ":" + name;
341
342        if (hash_params.xxh64) {
343
344            if (!hash_params.no_layer) {
345                // Per Layer Hash
346                XXH64_hash_t hash = XXH64(raw_data, n_bytes, 0);
347
348                char hex_result[17];
349                for (int  offset = 0; offset < 8; offset++) {
350                    unsigned int shift_bits_by = (8 * (8 - offset - 1));
351                    snprintf( ( hex_result + (2*offset)), sizeof(hex_result) - (2*offset), "%02x", (unsigned char) (hash >> shift_bits_by)&0xff);
352                }
353
354                if (hash_params.manifest_is_usable) {
355                    hash_manifest_result_t verify_result = manifest_verify(hash_params.manifest_file, HASH_TYPE_XXH64_STR, hex_result, tensor_layer_name);
356
357                    switch (verify_result) {
358                        case HASH_MANIFEST_NOT_FOUND:
359                            break;
360                        case HASH_MANIFEST_MISMATCH:
361                            tensor_layer_in_manifest = true;
362                            tensor_layer_has_mismatch = true;
363                            break;
364                        case HASH_MANIFEST_OK:
365                            tensor_layer_in_manifest = true;
366                            break;
367                    }
368
369                    printf("%-8s  %-s  %s  -  %s\n", HASH_TYPE_XXH64_STR, hex_result, tensor_layer_name.c_str(), hash_manifest_result_to_str(verify_result));
370                } else {
371                    printf("%-8s  %-s  %s\n", HASH_TYPE_XXH64_STR, hex_result, tensor_layer_name.c_str());
372                }
373            }
374
375            // Overall Model Hash
376            if (XXH64_update(xxh64_model_hash_state, raw_data, n_bytes) == XXH_ERROR) abort();
377        }
378
379        if (hash_params.sha1) {
380
381            if (!hash_params.no_layer) {
382                // Per Layer Hash
383                char result[21]; // sha1 outputs 20 bytes
384                SHA1( result, (const char *)raw_data, n_bytes);
385
386                char hex_result[41] = {0};
387                for (int  offset = 0; offset < 20; offset++) {
388                    snprintf( ( hex_result + (2*offset)), sizeof(hex_result) - (2*offset), "%02x", result[offset]&0xff);
389                }
390
391                if (hash_params.manifest_is_usable) {
392                    hash_manifest_result_t verify_result = manifest_verify(hash_params.manifest_file, HASH_TYPE_SHA1_STR, hex_result, tensor_layer_name);
393
394                    switch (verify_result) {
395                        case HASH_MANIFEST_NOT_FOUND:
396                            break;
397                        case HASH_MANIFEST_MISMATCH:
398                            tensor_layer_in_manifest = true;
399                            tensor_layer_has_mismatch = true;
400                            break;
401                        case HASH_MANIFEST_OK:
402                            tensor_layer_in_manifest = true;
403                            break;
404                    }
405
406                    printf("%-8s  %-s  %s  -  %s\n", HASH_TYPE_SHA1_STR, hex_result, tensor_layer_name.c_str(), hash_manifest_result_to_str(verify_result));
407                } else {
408                    printf("%-8s  %-s  %s\n", HASH_TYPE_SHA1_STR, hex_result, tensor_layer_name.c_str());
409                }
410            }
411
412            // Overall Model Hash
413            SHA1Update( &sha1_model_hash_ctx, (unsigned char const *)raw_data, n_bytes);
414        }
415
416        if (hash_params.sha256) {
417
418            if (!hash_params.no_layer) {
419                // Per Layer Hash
420                unsigned char result[SHA256_DIGEST_SIZE]; // sha256 outputs 32 bytes
421                sha256_hash((unsigned char*) result, (const unsigned char *)raw_data, n_bytes);
422
423                char hex_result[SHA256_DIGEST_SIZE * 2 + 1] = {0};
424                for (int  offset = 0; offset < SHA256_DIGEST_SIZE; offset++) {
425                    snprintf( ( hex_result + (2*offset)), sizeof(hex_result) - (2*offset), "%02x", result[offset]&0xff);
426                }
427
428                if (hash_params.manifest_is_usable) {
429                    hash_manifest_result_t verify_result = manifest_verify(hash_params.manifest_file, HASH_TYPE_SHA256_STR, hex_result, tensor_layer_name);
430
431                    switch (verify_result) {
432                        case HASH_MANIFEST_NOT_FOUND:
433                            break;
434                        case HASH_MANIFEST_MISMATCH:
435                            tensor_layer_in_manifest = true;
436                            tensor_layer_has_mismatch = true;
437                            break;
438                        case HASH_MANIFEST_OK:
439                            tensor_layer_in_manifest = true;
440                            break;
441                    }
442
443                    printf("%-8s  %-s  %s  -  %s\n", HASH_TYPE_SHA256_STR, hex_result, tensor_layer_name.c_str(), hash_manifest_result_to_str(verify_result));
444                } else {
445                    printf("%-8s  %-s  %s\n", HASH_TYPE_SHA256_STR, hex_result, tensor_layer_name.c_str());
446                }
447            }
448
449            // Overall Model Hash
450            sha256_update( &sha256_model_hash_ctx, (unsigned char const *)raw_data, n_bytes);
451        }
452
453        if (hash_params.uuid) {
454            SHA1Update( &sha1_for_uuid_ctx, (unsigned char const *)raw_data, n_bytes);
455        }
456    }
457
458    if (hash_params.xxh64) {
459        XXH64_hash_t const hash = XXH64_digest(xxh64_model_hash_state);
460
461        char hex_result[17];
462        for (int  offset = 0; offset < 8; offset++) {
463            unsigned int shift_bits_by = (8 * (8 - offset - 1));
464            snprintf( ( hex_result + (2*offset)), sizeof(hex_result) - (2*offset), "%02x", (unsigned char) (hash >> shift_bits_by)&0xff);
465        }
466
467        if (hash_params.manifest_is_usable) {
468            hash_manifest_result_t verify_result = manifest_verify(hash_params.manifest_file, HASH_TYPE_XXH64_STR, hex_result, fname);
469
470            switch (verify_result) {
471                case HASH_MANIFEST_NOT_FOUND:
472                    break;
473                case HASH_MANIFEST_MISMATCH:
474                    model_in_manifest = true;
475                    model_has_mismatch = true;
476                    break;
477                case HASH_MANIFEST_OK:
478                    model_in_manifest = true;
479                    break;
480            }
481
482            printf("%-8s  %-s  %s  -  %s\n", HASH_TYPE_XXH64_STR, hex_result, fname.c_str(), hash_manifest_result_to_str(verify_result));
483        } else {
484            printf("%-8s  %-s  %s\n", HASH_TYPE_XXH64_STR, hex_result, fname.c_str());
485        }
486    }
487
488    if (hash_params.sha1) {
489        unsigned char result[21];
490        SHA1Final(result, &sha1_model_hash_ctx);
491
492        char hex_result[41];
493        for (int  offset = 0; offset < 20; offset++) {
494            snprintf( ( hex_result + (2*offset)), sizeof(hex_result) - (2*offset), "%02x", result[offset]&0xff);
495        }
496
497        if (hash_params.manifest_is_usable) {
498            hash_manifest_result_t verify_result = manifest_verify(hash_params.manifest_file, HASH_TYPE_SHA1_STR, hex_result, fname);
499
500            switch (verify_result) {
501                case HASH_MANIFEST_NOT_FOUND:
502                    break;
503                case HASH_MANIFEST_MISMATCH:
504                    model_in_manifest = true;
505                    model_has_mismatch = true;
506                    break;
507                case HASH_MANIFEST_OK:
508                    model_in_manifest = true;
509                    break;
510            }
511
512            printf("%-8s  %-s  %s  -  %s\n", HASH_TYPE_SHA1_STR, hex_result, fname.c_str(), hash_manifest_result_to_str(verify_result));
513        } else {
514            printf("%-8s  %-s  %s\n", HASH_TYPE_SHA1_STR, hex_result, fname.c_str());
515        }
516    }
517
518    if (hash_params.sha256) {
519        unsigned char result[SHA256_DIGEST_SIZE]; // sha256 outputs 32 bytes
520        sha256_final( &sha256_model_hash_ctx,  result);
521
522        char hex_result[SHA256_DIGEST_SIZE * 2 + 1] = {0};
523        for (int  offset = 0; offset < SHA256_DIGEST_SIZE; offset++) {
524            snprintf( ( hex_result + (2*offset)), sizeof(hex_result) - (2*offset), "%02x", result[offset]&0xff);
525        }
526
527        if (hash_params.manifest_is_usable) {
528            hash_manifest_result_t verify_result = manifest_verify(hash_params.manifest_file, HASH_TYPE_SHA256_STR, hex_result, fname);
529
530            switch (verify_result) {
531                case HASH_MANIFEST_NOT_FOUND:
532                    break;
533                case HASH_MANIFEST_MISMATCH:
534                    model_in_manifest = true;
535                    model_has_mismatch = true;
536                    break;
537                case HASH_MANIFEST_OK:
538                    model_in_manifest = true;
539                    break;
540            }
541
542            printf("%-8s  %-s  %s  -  %s\n", HASH_TYPE_SHA256_STR, hex_result, fname.c_str(), hash_manifest_result_to_str(verify_result));
543        } else {
544            printf("%-8s  %-s  %s\n", HASH_TYPE_SHA256_STR, hex_result, fname.c_str());
545        }
546    }
547
548    if (hash_params.uuid) {
549        unsigned char result[21];
550        SHA1Final(result, &sha1_for_uuid_ctx);
551
552        unsigned char uuid[16];
553        generate_uuidv5(result, uuid);
554
555        char string_buffer[37] = {0};
556        snprintf(string_buffer, sizeof(string_buffer), "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
557            uuid[0], uuid[1], uuid[2], uuid[3],
558            uuid[4], uuid[5], uuid[6], uuid[7],
559            uuid[8], uuid[9], uuid[10], uuid[11],
560            uuid[12], uuid[13], uuid[14], uuid[15]);
561
562        if (hash_params.manifest_is_usable) {
563            hash_manifest_result_t verify_result = manifest_verify(hash_params.manifest_file, HASH_TYPE_SHA256_STR, string_buffer, fname);
564
565            switch (verify_result) {
566                case HASH_MANIFEST_NOT_FOUND:
567                    break;
568                case HASH_MANIFEST_MISMATCH:
569                    model_in_manifest = true;
570                    model_has_mismatch = true;
571                    break;
572                case HASH_MANIFEST_OK:
573                    model_in_manifest = true;
574                    break;
575            }
576
577            printf("%-8s  %-s  %s  -  %s\n", HASH_TYPE_UUID_STR, string_buffer, fname.c_str(), hash_manifest_result_to_str(verify_result));
578        } else {
579            printf("%-8s  %-s  %s\n", HASH_TYPE_UUID_STR, string_buffer, fname.c_str());
580        }
581    }
582
583
584    ggml_free(ctx_data);
585    gguf_free(ctx);
586
587
588    if (hash_params.manifest_is_usable) {
589        // In hash verification mode
590
591        if (!model_in_manifest) {
592            // model missing in manifest?
593
594            // Check tensor layer...
595            if (!tensor_layer_in_manifest) {
596                // Still missing? Maybe we are reading the wrong manifest.
597                return HASH_EXIT_MANIFEST_MISSING_ENTRY;
598            }
599
600            if (tensor_layer_has_mismatch) {
601                // Per tensor check found error
602                return HASH_EXIT_FAILURE;
603            }
604
605            // All per tensor layer checks passed? Sounds good enough.
606            return HASH_EXIT_SUCCESS;
607        }
608
609        // Overall model check passed, but let's check per layer just in case
610        // If missing, we don't care too much as the overall model checked
611        if (tensor_layer_in_manifest && tensor_layer_has_mismatch) {
612            return HASH_EXIT_FAILURE;
613        }
614
615        if (model_has_mismatch) {
616            // model has failed hash somewhere in the model
617            return HASH_EXIT_FAILURE;
618        }
619
620        // All checks appears to be fine
621        return HASH_EXIT_SUCCESS;
622    }
623
624    // In hash generation mode
625    return HASH_EXIT_SUCCESS;
626}
627
628int main(int argc, const char ** argv) {
629    hash_params params;
630    manifest_check_params manifest_check;
631    hash_params_parse(argc, argv, params);
632
633    if (!params.manifest_file.empty()) {
634        if (!manifest_type(params.manifest_file, manifest_check)) {
635            printf("ERROR cannot open manifest %s", params.manifest_file.c_str());
636            return HASH_EXIT_MANIFEST_FILE_ERROR;
637        }
638
639        if (!manifest_check.sha256 && !manifest_check.sha1 && !manifest_check.xxh64 && !manifest_check.uuid) {
640            printf("ERROR manifest does not have any known hash format in %s", params.manifest_file.c_str());
641            return HASH_EXIT_MANIFEST_UNKNOWN_HASH;
642        }
643
644        printf("manifest  %s", params.manifest_file.c_str());
645
646        if (manifest_check.sha256) {
647            printf("  sha256");
648        }
649
650        if (manifest_check.sha1) {
651            printf("  sha1");
652        }
653
654        if (manifest_check.xxh64) {
655            printf("  xxh64");
656        }
657
658        if (manifest_check.uuid) {
659            printf("  uuid");
660        }
661
662        printf("\n");
663
664        // Autoselect the highest security hash if manifest is provided but
665        // the user has not specifically defined the hash they care about
666        if (!params.xxh64 && !params.sha1 && !params.uuid && !params.sha256) {
667            // User has not selected a specific value, pick most secure hash
668            if (manifest_check.sha256) {
669                params.sha256 = true;
670            } else if (manifest_check.sha1) {
671                params.sha1 = true;
672            } else if (manifest_check.xxh64) {
673                params.xxh64 = true;
674            } else if (manifest_check.uuid) {
675                params.uuid = true;
676            }
677        }
678
679        params.manifest_is_usable = true;
680    }
681
682    // By default if no swich argument provided, assume xxh64
683    if (!params.xxh64 && !params.sha1 && !params.uuid && !params.sha256) {
684        params.xxh64 = true;
685    }
686
687    hash_exit_code_t exit_code = gguf_hash(params);
688
689    if (params.manifest_is_usable) {
690        printf("\nVerification results for %s - %s\n", params.manifest_file.c_str(), hash_exit_code_to_str(exit_code));
691    }
692
693    return exit_code;
694}