this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

trim

-742
-2
stack/crockford/.gitignore
··· 1 - _build 2 - .claude
-46
stack/crockford/.tangled/workflows/build.yml
··· 1 - when: 2 - - event: ["push", "pull_request"] 3 - branch: ["main"] 4 - 5 - engine: nixery 6 - 7 - dependencies: 8 - nixpkgs: 9 - - shell 10 - - stdenv 11 - - findutils 12 - - binutils 13 - - libunwind 14 - - ncurses 15 - - opam 16 - - git 17 - - gawk 18 - - gnupatch 19 - - gnum4 20 - - gnumake 21 - - gnutar 22 - - gnused 23 - - gnugrep 24 - - diffutils 25 - - gzip 26 - - bzip2 27 - - gcc 28 - - ocaml 29 - 30 - steps: 31 - - name: opam 32 - command: | 33 - opam init --disable-sandboxing -any 34 - - name: switch 35 - command: | 36 - opam install . --confirm-level=unsafe-yes --deps-only -t 37 - - name: build 38 - command: | 39 - opam exec -- dune build --verbose 40 - - name: test 41 - command: | 42 - opam exec -- dune runtest --verbose 43 - - name: doc 44 - command: | 45 - opam install -y odoc 46 - opam exec -- dune build @doc
-22
stack/crockford/LICENSE.md
··· 1 - MIT License 2 - 3 - Copyright (c) 2024-2025 Front Matter 4 - Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org> 5 - 6 - Permission is hereby granted, free of charge, to any person obtaining a copy 7 - of this software and associated documentation files (the "Software"), to deal 8 - in the Software without restriction, including without limitation the rights 9 - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 - copies of the Software, and to permit persons to whom the Software is 11 - furnished to do so, subject to the following conditions: 12 - 13 - The above copyright notice and this permission notice shall be included in all 14 - copies or substantial portions of the Software. 15 - 16 - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 - SOFTWARE.
-95
stack/crockford/README.md
··· 1 - # Crockford Base32 Encoding for OCaml 2 - 3 - An OCaml implementation of [Douglas Crockford's 4 - Base32](https://www.crockford.com/base32.html) encoding with ISO 7064 checksum 5 - support. Provides encoding and decoding of int64 values to URI-friendly base32 6 - strings, with optional checksum validation, padding, splitting, and random ID 7 - generation. Ported from <https://github.com/front-matter/commonmeta>. 8 - 9 - ## Installation 10 - 11 - ```bash 12 - opam install crockford 13 - ``` 14 - 15 - Or add to your `dune-project`: 16 - 17 - ```scheme 18 - (package 19 - (depends 20 - (crockford))) 21 - ``` 22 - 23 - ## Usage 24 - 25 - ### Basic Encoding and Decoding 26 - 27 - ```ocaml 28 - (* Encode a number *) 29 - let encoded = Crockford.encode 1234567890L in 30 - (* "16jkpa2" *) 31 - 32 - (* Decode back to number *) 33 - let decoded = Crockford.decode "16jkpa2" in 34 - (* 1234567890L *) 35 - ``` 36 - 37 - ### With Checksum 38 - 39 - ```ocaml 40 - (* Encode with checksum *) 41 - let encoded = Crockford.encode ~checksum:true 1234567890L in 42 - (* "16jkpa2d" *) 43 - 44 - (* Decode and validate checksum *) 45 - let decoded = Crockford.decode ~checksum:true "16jkpa2d" in 46 - (* 1234567890L - or raises Checksum_mismatch if invalid *) 47 - ``` 48 - 49 - ### Formatted Output 50 - 51 - ```ocaml 52 - (* Split with dashes for readability *) 53 - let encoded = Crockford.encode ~split_every:4 1234567890L in 54 - (* "16jk-pa2" *) 55 - 56 - (* With minimum length (zero-padded) *) 57 - let encoded = Crockford.encode ~min_length:10 1234L in 58 - (* "000000016j" *) 59 - ``` 60 - 61 - ### Random ID Generation 62 - 63 - ```ocaml 64 - Random.self_init (); 65 - 66 - (* Generate random IDs *) 67 - let id = Crockford.generate ~length:8 ~checksum:true () in 68 - (* e.g., "a3x7m9q5" *) 69 - 70 - (* Generate formatted IDs *) 71 - let id = Crockford.generate ~length:16 ~split_every:4 ~checksum:true () in 72 - (* e.g., "7n2q-8xkm-5pwt-3hr9" *) 73 - ``` 74 - 75 - ### Normalization 76 - 77 - ```ocaml 78 - (* Handles common character confusions *) 79 - let decoded = Crockford.decode "ILO" in (* Treated as "110" *) 80 - let decoded = Crockford.decode "16-JK-PA" in (* Dashes ignored *) 81 - ``` 82 - 83 - ## License 84 - 85 - MIT License 86 - 87 - ## Author 88 - 89 - Anil Madhavapeddy <anil@recoil.org> 90 - (based on code from https://github.com/front-matter/commonmeta) 91 - 92 - ## Links 93 - 94 - - [Homepage](https://tangled.org/@anil.recoil.org/ocaml-crockford) 95 - - [Crockford Base32 Specification](https://www.crockford.com/base32.html)
-4
stack/crockford/bin/dune
··· 1 - (executable 2 - (name roguedoi) 3 - (public_name roguedoi) 4 - (libraries crockford cmdliner))
-32
stack/crockford/bin/roguedoi.ml
··· 1 - (* roguedoi.ml - Generate random DOI identifiers with Crockford base32 encoding *) 2 - 3 - let generate_doi prefix length split = 4 - Random.self_init (); 5 - let suffix = Crockford.generate ~length ~split_every:split ~checksum:true () in 6 - Printf.printf "https://doi.org/%s/%s\n%!" prefix suffix 7 - 8 - let () = 9 - let open Cmdliner in 10 - 11 - let prefix = 12 - let doc = "DOI prefix to use (e.g., 10.59350)" in 13 - Arg.(value & opt string "10.59350" & info ["p"; "prefix"] ~docv:"PREFIX" ~doc) 14 - in 15 - 16 - let length = 17 - let doc = "Total length of the generated suffix (including checksum)" in 18 - Arg.(value & opt int 10 & info ["l"; "length"] ~docv:"LENGTH" ~doc) 19 - in 20 - 21 - let split = 22 - let doc = "Split the suffix every N characters with hyphens (0 = no splitting)" in 23 - Arg.(value & opt int 5 & info ["s"; "split"] ~docv:"SPLIT" ~doc) 24 - in 25 - 26 - let generate_cmd = 27 - let doc = "Generate a random DOI with Crockford base32 encoding" in 28 - let info = Cmd.info "roguedoi" ~version:"0.1.0" ~doc in 29 - Cmd.v info Term.(const generate_doi $ prefix $ length $ split) 30 - in 31 - 32 - exit (Cmd.eval generate_cmd)
-32
stack/crockford/crockford.opam
··· 1 - # This file is generated by dune, edit dune-project instead 2 - opam-version: "2.0" 3 - synopsis: "Crockford Base32 encoding for OCaml" 4 - description: 5 - "An OCaml implementation of Douglas Crockford's Base32 encoding with ISO 7064 checksum support. Provides encoding and decoding of int64 values to URI-friendly base32 strings, with optional checksum validation, padding, splitting, and random ID generation." 6 - maintainer: ["Anil Madhavapeddy <anil@recoil.org>"] 7 - authors: ["Anil Madhavapeddy"] 8 - license: "ISC" 9 - homepage: "https://tangled.org/@anil.recoil.org/ocaml-crockford" 10 - bug-reports: "https://tangled.org/@anil.recoil.org/ocaml-crockford/issues" 11 - depends: [ 12 - "dune" {>= "3.18"} 13 - "ocaml" {>= "4.14.1"} 14 - "odoc" {with-doc} 15 - "alcotest" {with-test & >= "1.9.0"} 16 - "cmdliner" {>= "2.0.0"} 17 - ] 18 - build: [ 19 - ["dune" "subst"] {dev} 20 - [ 21 - "dune" 22 - "build" 23 - "-p" 24 - name 25 - "-j" 26 - jobs 27 - "@install" 28 - "@runtest" {with-test} 29 - "@doc" {with-doc} 30 - ] 31 - ] 32 - x-maintenance-intent: ["(latest)"]
-26
stack/crockford/dune-project
··· 1 - (lang dune 3.18) 2 - 3 - (name crockford) 4 - 5 - (generate_opam_files true) 6 - 7 - (license ISC) 8 - (authors "Anil Madhavapeddy") 9 - (homepage "https://tangled.org/@anil.recoil.org/ocaml-crockford") 10 - (maintainers "Anil Madhavapeddy <anil@recoil.org>") 11 - (bug_reports "https://tangled.org/@anil.recoil.org/ocaml-crockford/issues") 12 - (maintenance_intent "(latest)") 13 - 14 - (package 15 - (name crockford) 16 - (synopsis "Crockford Base32 encoding for OCaml") 17 - (description 18 - "An OCaml implementation of Douglas Crockford's Base32 encoding with \ 19 - ISO 7064 checksum support. Provides encoding and decoding of int64 values \ 20 - to URI-friendly base32 strings, with optional checksum validation, padding, \ 21 - splitting, and random ID generation.") 22 - (depends 23 - (ocaml (>= 4.14.1)) 24 - (odoc :with-doc) 25 - (alcotest (and :with-test (>= 1.9.0))) 26 - (cmdliner (>= 2.0.0))))
-177
stack/crockford/lib/crockford.ml
··· 1 - type invalid_length = { length: int; message: string } 2 - type invalid_character = { char: char; message: string } 3 - type invalid_checksum = { checksum: string; message: string } 4 - type checksum_mismatch = { expected: int64; got: int64; identifier: string } 5 - 6 - type decode_error = 7 - | Invalid_length of invalid_length 8 - | Invalid_character of invalid_character 9 - | Invalid_checksum of invalid_checksum 10 - | Checksum_mismatch of checksum_mismatch 11 - 12 - exception Decode_error of decode_error 13 - 14 - let pp_invalid_length fmt { length; message } = 15 - Format.fprintf fmt "Invalid_length: length=%d, %s" length message 16 - 17 - let pp_invalid_character fmt { char; message } = 18 - Format.fprintf fmt "Invalid_character: char='%c', %s" char message 19 - 20 - let pp_invalid_checksum fmt { checksum; message } = 21 - Format.fprintf fmt "Invalid_checksum: checksum=%s, %s" checksum message 22 - 23 - let pp_checksum_mismatch fmt { expected; got; identifier } = 24 - Format.fprintf fmt "Checksum_mismatch: expected=%Ld, got=%Ld, identifier=%s" 25 - expected got identifier 26 - 27 - let pp_decode_error fmt = function 28 - | Invalid_length e -> pp_invalid_length fmt e 29 - | Invalid_character e -> pp_invalid_character fmt e 30 - | Invalid_checksum e -> pp_invalid_checksum fmt e 31 - | Checksum_mismatch e -> pp_checksum_mismatch fmt e 32 - 33 - let encoding_chars = "0123456789abcdefghjkmnpqrstvwxyz" 34 - 35 - let generate_checksum number = 36 - Int64.(sub (add (sub 97L (rem (mul 100L number) 97L)) 1L) 0L) 37 - 38 - let validate number ~checksum = 39 - Int64.equal checksum (generate_checksum number) 40 - 41 - let normalize str = 42 - let len = String.length str in 43 - let buf = Bytes.create len in 44 - let rec process i j = 45 - if i >= len then Bytes.sub_string buf 0 j 46 - else 47 - let c = String.get str i in 48 - let c_lower = Char.lowercase_ascii c in 49 - match c_lower with 50 - | '-' -> process (i + 1) j 51 - | 'i' | 'l' -> Bytes.set buf j '1'; process (i + 1) (j + 1) 52 - | 'o' -> Bytes.set buf j '0'; process (i + 1) (j + 1) 53 - | _ -> Bytes.set buf j c_lower; process (i + 1) (j + 1) 54 - in 55 - process 0 0 56 - 57 - let encode ?(split_every=0) ?(min_length=0) ?(checksum=false) number = 58 - let original_number = number in 59 - 60 - (* Build base32 encoding *) 61 - let rec build_encoding acc n = 62 - if Int64.equal n 0L then acc 63 - else 64 - let remainder = Int64.to_int (Int64.rem n 32L) in 65 - let n' = Int64.div n 32L in 66 - build_encoding (encoding_chars.[remainder] :: acc) n' 67 - in 68 - 69 - let encoded_list = 70 - if Int64.equal number 0L then ['0'] 71 - else build_encoding [] number 72 - in 73 - 74 - let encoded_str = String.concat "" (List.map (String.make 1) encoded_list) in 75 - 76 - (* Adjust min_length if checksum is enabled *) 77 - let adjusted_length = 78 - if checksum && min_length > 2 then min_length - 2 79 - else min_length 80 - in 81 - 82 - (* Pad with zeros if needed *) 83 - let padded = 84 - if adjusted_length > 0 && String.length encoded_str < adjusted_length then 85 - String.make (adjusted_length - String.length encoded_str) '0' ^ encoded_str 86 - else 87 - encoded_str 88 - in 89 - 90 - (* Add checksum *) 91 - let with_checksum = 92 - if checksum then 93 - let cs = generate_checksum original_number in 94 - padded ^ Printf.sprintf "%02Ld" cs 95 - else 96 - padded 97 - in 98 - 99 - (* Split if requested *) 100 - if split_every > 0 then 101 - let len = String.length with_checksum in 102 - let num_splits = (len + split_every - 1) / split_every in 103 - let splits = Array.make num_splits "" in 104 - for i = 0 to num_splits - 1 do 105 - let start = i * split_every in 106 - let chunk_len = min split_every (len - start) in 107 - splits.(i) <- String.sub with_checksum start chunk_len 108 - done; 109 - String.concat "-" (Array.to_list splits) 110 - else 111 - with_checksum 112 - 113 - let decode ?(checksum=false) str = 114 - let encoded = normalize str in 115 - 116 - let (encoded_part, checksum_value) = 117 - if checksum then begin 118 - if String.length encoded < 3 then 119 - raise (Decode_error (Invalid_checksum { 120 - checksum = encoded; 121 - message = "encoded string too short for checksum" 122 - })); 123 - 124 - let cs_str = String.sub encoded (String.length encoded - 2) 2 in 125 - let cs = 126 - try Int64.of_string cs_str 127 - with Failure _ -> 128 - raise (Decode_error (Invalid_checksum { 129 - checksum = cs_str; 130 - message = "invalid checksum format" 131 - })) 132 - in 133 - (String.sub encoded 0 (String.length encoded - 2), Some cs) 134 - end else 135 - (encoded, None) 136 - in 137 - 138 - (* Decode base32 *) 139 - let number = ref 0L in 140 - String.iter (fun c -> 141 - number := Int64.mul !number 32L; 142 - match String.index_opt encoding_chars c with 143 - | Some pos -> number := Int64.add !number (Int64.of_int pos) 144 - | None -> 145 - raise (Decode_error (Invalid_character { 146 - char = c; 147 - message = Printf.sprintf "character '%c' not in base32 alphabet" c 148 - })) 149 - ) encoded_part; 150 - 151 - (* Validate checksum if present *) 152 - (match checksum_value with 153 - | Some cs -> 154 - if not (validate !number ~checksum:cs) then 155 - raise (Decode_error (Checksum_mismatch { 156 - expected = generate_checksum !number; 157 - got = cs; 158 - identifier = str 159 - })) 160 - | None -> ()); 161 - 162 - !number 163 - 164 - let generate ~length ?(split_every=0) ?(checksum=false) () = 165 - if checksum && length < 3 then 166 - raise (Decode_error (Invalid_length { 167 - length; 168 - message = "length must be >= 3 if checksum is enabled" 169 - })); 170 - 171 - let adjusted_length = if checksum then length - 2 else length in 172 - 173 - (* Generate random number between 0 and 32^length *) 174 - let max_val = 32.0 ** float_of_int adjusted_length in 175 - let random_num = Int64.of_float (Random.float max_val) in 176 - 177 - encode ~split_every ~min_length:adjusted_length ~checksum random_num
-89
stack/crockford/lib/crockford.mli
··· 1 - (** Crockford Base32 encoding for OCaml *) 2 - 3 - (** {1 Error Types} *) 4 - 5 - type invalid_length = { length: int; message: string } 6 - (** Error for invalid length parameters *) 7 - 8 - type invalid_character = { char: char; message: string } 9 - (** Error for invalid characters during decoding *) 10 - 11 - type invalid_checksum = { checksum: string; message: string } 12 - (** Error for invalid checksum format *) 13 - 14 - type checksum_mismatch = { expected: int64; got: int64; identifier: string } 15 - (** Error for checksum validation failures *) 16 - 17 - type decode_error = 18 - | Invalid_length of invalid_length 19 - | Invalid_character of invalid_character 20 - | Invalid_checksum of invalid_checksum 21 - | Checksum_mismatch of checksum_mismatch 22 - (** Union of all possible decode errors *) 23 - 24 - exception Decode_error of decode_error 25 - (** Main exception raised for all decoding errors *) 26 - 27 - val pp_invalid_length : Format.formatter -> invalid_length -> unit 28 - (** Pretty-print an invalid_length error *) 29 - 30 - val pp_invalid_character : Format.formatter -> invalid_character -> unit 31 - (** Pretty-print an invalid_character error *) 32 - 33 - val pp_invalid_checksum : Format.formatter -> invalid_checksum -> unit 34 - (** Pretty-print an invalid_checksum error *) 35 - 36 - val pp_checksum_mismatch : Format.formatter -> checksum_mismatch -> unit 37 - (** Pretty-print a checksum_mismatch error *) 38 - 39 - val pp_decode_error : Format.formatter -> decode_error -> unit 40 - (** Pretty-print a decode_error *) 41 - 42 - (** {1 Constants} *) 43 - 44 - val encoding_chars : string 45 - (** The Crockford base32 encoding alphabet (excludes i, l, o, u) *) 46 - 47 - (** {1 Encoding and Decoding} *) 48 - 49 - val encode : 50 - ?split_every:int -> 51 - ?min_length:int -> 52 - ?checksum:bool -> 53 - int64 -> string 54 - (** [encode ?split_every ?min_length ?checksum n] encodes an int64 to a Crockford base32 string. 55 - @param split_every Split the output with '-' every n characters (default: no splitting) 56 - @param min_length Pad with zeros to this minimum length (default: no padding) 57 - @param checksum Append ISO 7064 checksum as 2 digits (default: false) *) 58 - 59 - val decode : ?checksum:bool -> string -> int64 60 - (** [decode ?checksum str] decodes a Crockford base32 string to int64. 61 - @param checksum Expect and validate ISO 7064 checksum (default: false) 62 - @raise Decode_error if decoding fails (invalid characters, invalid checksum format, or checksum mismatch) *) 63 - 64 - (** {1 ID Generation} *) 65 - 66 - val generate : 67 - length:int -> 68 - ?split_every:int -> 69 - ?checksum:bool -> 70 - unit -> string 71 - (** [generate ~length ?split_every ?checksum ()] generates a random Crockford base32 string. 72 - @param length The length of the generated string (excluding checksum) 73 - @param split_every Split the output with '-' every n characters (default: no splitting) 74 - @param checksum Append ISO 7064 checksum as 2 digits (default: false) 75 - @raise Decode_error if checksum is true and length < 3 76 - 77 - Note: Caller must initialize Random module with {!Random.self_init} before use *) 78 - 79 - (** {1 Utility Functions} *) 80 - 81 - val normalize : string -> string 82 - (** [normalize str] normalizes a string for decoding by converting to lowercase, 83 - removing dashes, and mapping confusable characters (i→1, l→1, o→0) *) 84 - 85 - val validate : int64 -> checksum:int64 -> bool 86 - (** [validate n ~checksum] validates that a checksum matches the number *) 87 - 88 - val generate_checksum : int64 -> int64 89 - (** [generate_checksum n] generates an ISO 7064 (mod 97-10) checksum for a number *)
-3
stack/crockford/lib/dune
··· 1 - (library 2 - (name crockford) 3 - (public_name crockford))
-3
stack/crockford/test/dune
··· 1 - (test 2 - (name test_crockford) 3 - (libraries crockford alcotest))
-211
stack/crockford/test/test_crockford.ml
··· 1 - (* Test suite using Alcotest - ported from crockford_test.go *) 2 - 3 - (* Encode tests *) 4 - let test_encode () = 5 - let test_cases = [ 6 - (0L, 0, 0, false, "0"); 7 - (1234L, 0, 0, false, "16j"); 8 - (1234L, 2, 0, false, "16-j"); 9 - (1234L, 2, 4, false, "01-6j"); 10 - (538751765283013L, 5, 10, false, "f9zqn-sf065"); 11 - (736381604818L, 5, 10, true, "ndsw7-4yj20"); 12 - (258706475165200172L, 7, 14, true, "75rw5cg-n1bsc64"); 13 - (161006169L, 4, 8, true, "4shg-js75"); 14 - ] in 15 - List.iter (fun (input, split_every, length, checksum, expected) -> 16 - let result = Crockford.encode ~split_every ~min_length:length ~checksum input in 17 - let test_name = Printf.sprintf "encode %Ld (split=%d, len=%d, checksum=%b)" input split_every length checksum in 18 - Alcotest.(check string) test_name expected result 19 - ) test_cases 20 - 21 - (* Generate tests *) 22 - let test_generate () = 23 - Random.self_init (); 24 - 25 - let test_cases = [ 26 - (4, 0, false); 27 - (10, 5, false); 28 - (10, 5, true); 29 - ] in 30 - List.iter (fun (length, split_every, checksum) -> 31 - let result = Crockford.generate ~length ~split_every ~checksum () in 32 - let expected_length = 33 - if split_every > 0 then 34 - length + (length / split_every) - 1 35 - else 36 - length 37 - in 38 - let test_name = Printf.sprintf "generate length=%d split=%d checksum=%b" length split_every checksum in 39 - Alcotest.(check int) test_name expected_length (String.length result) 40 - ) test_cases 41 - 42 - (* Decode tests *) 43 - let test_decode () = 44 - let test_cases = [ 45 - ("0", false, Some 0L); 46 - ("16j", false, Some 1234L); 47 - ("16-j", false, Some 1234L); 48 - ("01-6j", false, Some 1234L); 49 - ("f9zqn-sf065", false, Some 538751765283013L); 50 - ("twwjw-1ww98", true, Some 924377286556L); 51 - ("9ed5m-ytn", false, Some 324712168277L); 52 - ("9ed5m-ytn30", true, Some 324712168277L); 53 - ("elife.01567", false, None); (* Should fail - contains invalid character '.' *) 54 - ] in 55 - List.iter (fun (input, checksum, expected) -> 56 - let test_name = Printf.sprintf "decode '%s' (checksum=%b)" input checksum in 57 - match expected with 58 - | Some want -> 59 - let got = Crockford.decode ~checksum input in 60 - Alcotest.(check int64) test_name want got 61 - | None -> 62 - (* Expected to fail *) 63 - Alcotest.check_raises 64 - test_name 65 - (Crockford.Decode_error (Crockford.Invalid_character { 66 - char = '.'; 67 - message = "character '.' not in base32 alphabet" 68 - })) 69 - (fun () -> ignore (Crockford.decode ~checksum input)) 70 - ) test_cases 71 - 72 - (* Normalize tests *) 73 - let test_normalize () = 74 - let test_cases = [ 75 - ("f9ZQNSF065", "f9zqnsf065"); 76 - ("f9zqn-sf065", "f9zqnsf065"); 77 - ("f9Llio", "f91110"); 78 - ] in 79 - List.iter (fun (input, expected) -> 80 - let result = Crockford.normalize input in 81 - let test_name = Printf.sprintf "normalize '%s'" input in 82 - Alcotest.(check string) test_name expected result 83 - ) test_cases 84 - 85 - (* GenerateChecksum tests *) 86 - let test_generate_checksum () = 87 - let test_cases = [ 88 - (450320459383L, 85L); 89 - (123456789012L, 44L); 90 - ] in 91 - List.iter (fun (input, expected) -> 92 - let result = Crockford.generate_checksum input in 93 - let test_name = Printf.sprintf "generate_checksum %Ld" input in 94 - Alcotest.(check int64) test_name expected result 95 - ) test_cases 96 - 97 - (* Validate tests *) 98 - let test_validate () = 99 - let test_cases = [ 100 - (375301249367L, 92L, true); 101 - (930412369850L, 36L, true); 102 - ] in 103 - List.iter (fun (input, checksum, expected) -> 104 - let result = Crockford.validate input ~checksum in 105 - let test_name = Printf.sprintf "validate %Ld checksum=%Ld" input checksum in 106 - Alcotest.(check bool) test_name expected result 107 - ) test_cases 108 - 109 - (* Additional roundtrip tests *) 110 - let test_roundtrip () = 111 - let test_numbers = [ 112 - 0L; 113 - 1L; 114 - 32L; 115 - 1024L; 116 - 1234567890L; 117 - Int64.max_int; 118 - ] in 119 - 120 - List.iter (fun num -> 121 - let encoded = Crockford.encode num in 122 - let decoded = Crockford.decode encoded in 123 - Alcotest.(check int64) (Printf.sprintf "roundtrip %Ld" num) num decoded 124 - ) test_numbers 125 - 126 - let test_roundtrip_with_checksum () = 127 - let test_numbers = [ 128 - 0L; 129 - 1L; 130 - 32L; 131 - 1024L; 132 - 1234567890L; 133 - ] in 134 - 135 - List.iter (fun num -> 136 - let encoded = Crockford.encode ~checksum:true num in 137 - let decoded = Crockford.decode ~checksum:true encoded in 138 - Alcotest.(check int64) (Printf.sprintf "roundtrip with checksum %Ld" num) num decoded 139 - ) test_numbers 140 - 141 - (* Error handling tests *) 142 - let test_error_invalid_character () = 143 - Alcotest.check_raises 144 - "invalid character" 145 - (Crockford.Decode_error (Crockford.Invalid_character { 146 - char = '#'; 147 - message = "character '#' not in base32 alphabet" 148 - })) 149 - (fun () -> ignore (Crockford.decode "abc#def")) 150 - 151 - let test_error_invalid_checksum () = 152 - Alcotest.check_raises 153 - "invalid checksum length" 154 - (Crockford.Decode_error (Crockford.Invalid_checksum { 155 - checksum = "ab"; 156 - message = "encoded string too short for checksum" 157 - })) 158 - (fun () -> ignore (Crockford.decode ~checksum:true "ab")) 159 - 160 - let test_error_checksum_mismatch () = 161 - let encoded = Crockford.encode ~checksum:true 1234L in 162 - let corrupted = String.sub encoded 0 (String.length encoded - 2) ^ "00" in 163 - 164 - Alcotest.check_raises 165 - "checksum mismatch" 166 - (Crockford.Decode_error (Crockford.Checksum_mismatch { 167 - expected = Crockford.generate_checksum 1234L; 168 - got = 0L; 169 - identifier = corrupted 170 - })) 171 - (fun () -> ignore (Crockford.decode ~checksum:true corrupted)) 172 - 173 - let test_error_invalid_length () = 174 - Alcotest.check_raises 175 - "invalid length for generate" 176 - (Crockford.Decode_error (Crockford.Invalid_length { 177 - length = 2; 178 - message = "length must be >= 3 if checksum is enabled" 179 - })) 180 - (fun () -> ignore (Crockford.generate ~length:2 ~checksum:true ())) 181 - 182 - let () = 183 - let open Alcotest in 184 - run "Crockford" [ 185 - "encoding", [ 186 - test_case "encode" `Quick test_encode; 187 - ]; 188 - "decoding", [ 189 - test_case "decode" `Quick test_decode; 190 - ]; 191 - "normalization", [ 192 - test_case "normalize" `Quick test_normalize; 193 - ]; 194 - "checksum", [ 195 - test_case "generate_checksum" `Quick test_generate_checksum; 196 - test_case "validate" `Quick test_validate; 197 - ]; 198 - "generation", [ 199 - test_case "generate" `Quick test_generate; 200 - ]; 201 - "roundtrip", [ 202 - test_case "roundtrip encoding/decoding" `Quick test_roundtrip; 203 - test_case "roundtrip with checksum" `Quick test_roundtrip_with_checksum; 204 - ]; 205 - "errors", [ 206 - test_case "invalid character" `Quick test_error_invalid_character; 207 - test_case "invalid checksum" `Quick test_error_invalid_checksum; 208 - test_case "checksum mismatch" `Quick test_error_checksum_mismatch; 209 - test_case "invalid length" `Quick test_error_invalid_length; 210 - ]; 211 - ]