···4040(package
4141 (name ipld)
4242 (synopsis "A DASL-compliant implementation of some IPLD formats")
4343- (description "Currently includes DAG-CBOR and CIDv1")
4343+ (description "Currently includes DAG-CBOR, CIDv1, and CARv1")
4444 (allow_empty)
4545 (depends
4646 ocaml
4747 dune
4848 (base64 (>= 3.5.1))
4949 (digestif (>= 1.2.0))
5050+ lwt
5051 (multibase (>= 0.1.0))
5152 (yojson (>= 3.0.0))
5353+ (lwt_ppx (>= 5.9.1))
5254 (alcotest :with-test)))
+3-1
ipld.opam
···11# This file is generated by dune, edit dune-project instead
22opam-version: "2.0"
33synopsis: "A DASL-compliant implementation of some IPLD formats"
44-description: "Currently includes DAG-CBOR and CIDv1"
44+description: "Currently includes DAG-CBOR, CIDv1, and CARv1"
55maintainer: ["futurGH"]
66authors: ["futurGH"]
77license: "MPL-2.0"
···1212 "dune" {>= "3.14"}
1313 "base64" {>= "3.5.1"}
1414 "digestif" {>= "1.2.0"}
1515+ "lwt"
1516 "multibase" {>= "0.1.0"}
1617 "yojson" {>= "3.0.0"}
1818+ "lwt_ppx" {>= "5.9.1"}
1719 "alcotest" {with-test}
1820 "odoc" {with-doc}
1921]
+152
ipld/lib/car.ml
···11+(* varint encoding/decoding; ripped from https://github.com/chrisdickinson/varint *)
22+module Varint = struct
33+ let bytes = ref 0
44+55+ let msb = 0x80
66+77+ let rest = 0x7F
88+99+ let msball = lnot rest
1010+1111+ let int_threshold = 1 lsl 31
1212+1313+ let encode (n0 : int) : bytes =
1414+ if n0 < 0 then (
1515+ bytes := 0 ;
1616+ failwith "negative numbers not supported" ) ;
1717+ let num = ref n0 in
1818+ let buf = Buffer.create 10 in
1919+ let offset = ref 0 in
2020+ while !num >= int_threshold do
2121+ let byte = !num land 0xFF lor msb in
2222+ Buffer.add_char buf (Char.chr (byte land 0xFF)) ;
2323+ incr offset ;
2424+ num := !num / 128
2525+ done ;
2626+ while !num land msball <> 0 do
2727+ let byte = !num land 0xFF lor msb in
2828+ Buffer.add_char buf (Char.chr (byte land 0xFF)) ;
2929+ incr offset ;
3030+ num := !num lsr 7
3131+ done ;
3232+ let last = !num in
3333+ Buffer.add_char buf (Char.chr (last land 0xFF)) ;
3434+ incr offset ;
3535+ bytes := !offset ;
3636+ Bytes.unsafe_of_string (Buffer.contents buf)
3737+3838+ let decode buf =
3939+ let l = Bytes.length buf in
4040+ let rec aux res shift counter =
4141+ if counter >= l || shift > 49 then failwith "could not decode varint"
4242+ else
4343+ let b = Bytes.get_uint8 buf counter in
4444+ let new_res =
4545+ if shift < 28 then res + ((b land rest) lsl shift)
4646+ else res + (b land rest * (1 lsl shift))
4747+ in
4848+ let new_counter = counter + 1 in
4949+ let new_shift = shift + 7 in
5050+ if b >= msb then aux new_res new_shift new_counter
5151+ else (new_res, new_counter)
5252+ in
5353+ let result, final_counter = aux 0 0 0 in
5454+ (result, final_counter)
5555+end
5656+5757+(* converts a series of mst blocks into a car stream *)
5858+let blocks_to_stream (root : Cid.t option) (blocks : (Cid.t * bytes) Lwt_seq.t)
5959+ : bytes Lwt_seq.t =
6060+ let header =
6161+ Dag_cbor.encode
6262+ (`Map
6363+ (Dag_cbor.StringMap.of_list
6464+ [ ("version", `Integer 1L)
6565+ ; ( "roots"
6666+ , `Array
6767+ (match root with None -> [||] | Some root -> [|`Link root|])
6868+ ) ] ) )
6969+ in
7070+ let seq = Lwt_seq.of_list [Varint.encode (Bytes.length header); header] in
7171+ Lwt_seq.append seq
7272+ (Lwt_seq.flat_map
7373+ (fun (cid, block) ->
7474+ Lwt_seq.of_list
7575+ [ Varint.encode
7676+ ((cid |> Cid.to_bytes |> Bytes.length) + Bytes.length block)
7777+ ; cid.bytes
7878+ ; block ] )
7979+ blocks )
8080+8181+(* converts a series of mst blocks into a car file *)
8282+let blocks_to_car (root : Cid.t option) (blocks : (Cid.t * bytes) Lwt_seq.t) :
8383+ bytes Lwt.t =
8484+ let stream = blocks_to_stream root blocks in
8585+ let buf = Buffer.create 1024 in
8686+ let%lwt () = Lwt_seq.iter (Buffer.add_bytes buf) stream in
8787+ Lwt.return (Buffer.to_bytes buf)
8888+8989+(* reads a car stream into a serialized mst
9090+ returns (roots, blocks) *)
9191+let read_car_stream (stream : bytes Lwt_seq.t) :
9292+ (Cid.t list * (Cid.t * bytes) Lwt_seq.t) Lwt.t =
9393+ let buf = Buffer.create 1024 in
9494+ let%lwt () = Lwt_seq.iter (Buffer.add_bytes buf) stream in
9595+ let bytes = Buffer.to_bytes buf in
9696+ let bytes_len = Bytes.length bytes in
9797+ let pos = ref 0 in
9898+ let read_varint () =
9999+ if !pos >= bytes_len then None
100100+ else
101101+ let n, used = Varint.decode (Bytes.sub bytes !pos (bytes_len - !pos)) in
102102+ pos := !pos + used ;
103103+ Some n
104104+ in
105105+ let read_bytes n =
106106+ if !pos + n > bytes_len then failwith "unexpected end of car stream"
107107+ else
108108+ let b = Bytes.sub bytes !pos n in
109109+ pos := !pos + n ;
110110+ b
111111+ in
112112+ let header_size =
113113+ match read_varint () with
114114+ | None ->
115115+ failwith "could not parse car header"
116116+ | Some n ->
117117+ n
118118+ in
119119+ let header_bytes = read_bytes header_size in
120120+ let header = Dag_cbor.decode header_bytes in
121121+ let roots =
122122+ match header with
123123+ | `Map m -> (
124124+ let roots_v =
125125+ try Some (Dag_cbor.StringMap.find "roots" m) with Not_found -> None
126126+ in
127127+ match roots_v with
128128+ | Some (`Array arr) ->
129129+ Array.fold_right
130130+ (fun v acc -> match v with `Link cid -> cid :: acc | _ -> acc)
131131+ arr []
132132+ | _ ->
133133+ [] )
134134+ | _ ->
135135+ []
136136+ in
137137+ let rec read_blocks acc =
138138+ if !pos >= bytes_len then List.rev acc
139139+ else
140140+ match read_varint () with
141141+ | None ->
142142+ List.rev acc
143143+ | Some block_size ->
144144+ if block_size <= 0 then read_blocks acc
145145+ else
146146+ let block_bytes = read_bytes block_size in
147147+ let cid, remainder = Cid.decode_first block_bytes in
148148+ read_blocks ((cid, remainder) :: acc)
149149+ in
150150+ let blocks_list = read_blocks [] in
151151+ let blocks_seq = Lwt_seq.of_list blocks_list in
152152+ Lwt.return (roots, blocks_seq)