Skip to content

Commit

Permalink
Tidy up (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
patricoferris committed Mar 22, 2023
1 parent aecf0cc commit 517cd8f
Show file tree
Hide file tree
Showing 14 changed files with 373 additions and 184 deletions.
2 changes: 1 addition & 1 deletion .ocamlformat
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version=0.23.0
version=0.25.1
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ ocaml-cid

[Content-addressed identifiers](https://docs.ipfs.io/concepts/content-addressing/) in OCaml.


## Quick Example

```ocaml
# let s = "zb2rhe5P4gXftAwvA4eXQ5HJwsER2owDyS9sKaQRRVQPn93bA";;
val s : string = "zb2rhe5P4gXftAwvA4eXQ5HJwsER2owDyS9sKaQRRVQPn93bA"
Expand All @@ -13,6 +16,8 @@ cidv1 - base58btc - raw - ident(sha2-256) length(32) digest(6e 6f f7 95 0a 36 18
68 6c d7 d7 e3 c0 fc 42 ee 03 30 07 2d 24 5c 95
)
- : unit = ()
# Result.get_ok @@ Cid.to_string cid;;
# Cid.to_string cid;;
- : string = "zb2rhe5P4gXftAwvA4eXQ5HJwsER2owDyS9sKaQRRVQPn93bA"
```

See [test/irmin_cid.ml](https://github.com/patricoferris/ocaml-cid/blob/main/test/irmin_cid.ml) to see how they can be used for Irmin store hashing.
13 changes: 4 additions & 9 deletions cid.opam
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ authors: ["patrick@sirref.org"]
homepage: "https://github.com/patricoferris/ocaml-cid"
bug-reports: "https://github.com/patricoferris/ocaml-cid/issues"
depends: [
"dune" {>= "2.9"}
"dune" {>= "3.2"}
"multibase"
"multicodec"
"multihash-digestif"
"cstruct"
"alcotest" {with-test}
"mdx" {with-test & >= "2.2.1"}
"odoc" {with-doc}
]
build: [
Expand All @@ -22,15 +23,9 @@ build: [
name
"-j"
jobs
"--promote-install-files=false"
"@install"
"@runtest" {with-test}
"@doc" {with-doc}
]
["dune" "install" "-p" name "--create-install-files" name]
]
dev-repo: "git+https://github.com/patricoferris/ocaml-cid.git"
pin-depends: [
[ "multibase.dev" "git+https://github.com/patricoferris/ocaml-multibase" ]
[ "multihash-digestif.dev" "git+https://github.com/patricoferris/ocaml-multihash" ]
]
dev-repo: "git+https://github.com/patricoferris/ocaml-cid.git"
2 changes: 1 addition & 1 deletion dune
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(mdx
(packages cid)
(libraries cid)
(preludes doc/prelude.txt)
(files README.md))
4 changes: 2 additions & 2 deletions dune-project
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
(lang dune 2.9)
(using mdx 0.1)
(lang dune 3.2)
(using mdx 0.3)
(name cid)
247 changes: 131 additions & 116 deletions src/cid.ml
Original file line number Diff line number Diff line change
@@ -1,118 +1,133 @@
module Md = Multihash_digestif

type version = [ `Cidv0 | `Cidv1 | `Cidv2 | `Cidv3 ]
(* Compatible with Multicodec.ipld *)

let version_string = function
| `Cidv0 -> "Cidv0"
| (`Cidv1 | `Cidv2 | `Cidv3) as v -> Multicodec.cid_to_string v

let version_of_ipld = function
| (`Cidv1 | `Cidv2 | `Cidv3) as v -> v
| _ -> invalid_arg "Expected a CID version"

type t = {
version : version;
base : Multibase.Encoding.t;
codec : Multicodec.t;
hash : Cstruct.t Md.t;
}

let v ~version ~base ~codec ~hash = { version; base; codec; hash }
let version t = t.version
let base t = t.base
let codec t = t.codec
let hash t = t.hash

let equal a b =
a.version = b.version && a.base = b.base && a.codec = b.codec
&& Md.equal a.hash b.hash

let cidv0_of_string ~base buf =
match Md.read_buff buf with
| Ok hash -> { version = `Cidv0; base; codec = `Dag_pb; hash }
| Error (`Msg m) -> failwith ("Failed parsing Cidv0: " ^ m)

let of_cstruct ~base buf =
let l = Cstruct.length buf in
if l = 34 && Cstruct.get_uint8 buf 0 = 0x12 && Cstruct.get_uint8 buf 1 = 0x20
then Ok (cidv0_of_string ~base buf)
else
let v, off = Multihash.Uvarint.decode buf in
let c, off' = Multihash.Uvarint.decode Cstruct.(sub buf off (l - off)) in
match Md.read_buff Cstruct.(sub buf (off + off') (l - off - off')) with
| Ok hash ->
let version =
version_of_ipld (Option.get @@ Multicodec.cid_of_code v)
in
let codec = Option.get @@ Multicodec.of_code c in
let v = Ok { version; codec; base; hash } in
(v
:> ( t,
[ `Msg of string | `Unsupported of Multibase.Encoding.t ] )
result)
| Error _ as e ->
(e
:> ( t,
[ `Msg of string | `Unsupported of Multibase.Encoding.t ] )
result)

let ( <+> ) = Cstruct.append

let to_cstruct { version; base; codec; hash } =
match version with
| `Cidv0 -> (
match base with
| `Base58btc ->
let hash = Md.write hash in
let b = Multibase.Base58.encode (Cstruct.to_string hash) in
Cstruct.of_string b
| _ ->
let hash = Md.write hash in
let b =
Multibase.encode base (Cstruct.to_string hash) |> Result.get_ok
include Cid_intf

module Make (Md : Multihash.S) = struct
type multihash = Cstruct.t Md.t

(* Compatible with Multicodec.ipld *)

let version_string = function
| `Cidv0 -> "Cidv0"
| (`Cidv1 | `Cidv2 | `Cidv3) as v -> Multicodec.cid_to_string v

let version_of_ipld = function
| (`Cidv1 | `Cidv2 | `Cidv3) as v -> v
| _ -> invalid_arg "Expected a CID version"

type t = {
version : version;
base : Multibase.Encoding.t;
codec : Multicodec.t;
hash : Cstruct.t Md.t;
}

let v ~version ~base ~codec ~hash = { version; base; codec; hash }
let version t = t.version
let base t = t.base
let codec t = t.codec
let hash t = t.hash

let equal a b =
a.version = b.version && a.base = b.base && a.codec = b.codec
&& Md.equal a.hash b.hash

let cidv0_of_string ~base buf =
match Md.read_buff buf with
| Ok hash -> { version = `Cidv0; base; codec = `Dag_pb; hash }
| Error (`Msg m) -> failwith ("Failed parsing Cidv0: " ^ m)

let of_cstruct ~base buf =
let l = Cstruct.length buf in
if
l = 34 && Cstruct.get_uint8 buf 0 = 0x12 && Cstruct.get_uint8 buf 1 = 0x20
then Ok (cidv0_of_string ~base buf)
else
let v, off = Multihash.Uvarint.decode buf in
let c, off' = Multihash.Uvarint.decode Cstruct.(sub buf off (l - off)) in
match Md.read_buff Cstruct.(sub buf (off + off') (l - off - off')) with
| Ok hash ->
let version =
version_of_ipld (Option.get @@ Multicodec.cid_of_code v)
in
Cstruct.of_string b)
| (`Cidv1 | `Cidv2 | `Cidv3) as version ->
let enc =
Multibase.Encoding.to_code base |> fun s -> String.get_uint8 s 0
in
let ver =
Multicodec.cid_to_code (version :> Multicodec.cid)
|> Multihash.Uvarint.encode
let codec = Option.get @@ Multicodec.of_code c in
let v = Ok { version; codec; base; hash } in
(v
:> ( t,
[ `Msg of string | `Unsupported of Multibase.Encoding.t ] )
result)
| Error _ as e ->
(e
:> ( t,
[ `Msg of string | `Unsupported of Multibase.Encoding.t ] )
result)

let ( <+> ) = Cstruct.append

let to_cstruct { version; base; codec; hash } =
match version with
| `Cidv0 -> (
match base with
| `Base58btc ->
let hash = Md.write hash in
let b = Multibase.Base58.encode (Cstruct.to_string hash) in
Cstruct.of_string b
| _ ->
let hash = Md.write hash in
let b =
Multibase.encode base (Cstruct.to_string hash) |> Result.get_ok
in
Cstruct.of_string b)
| (`Cidv1 | `Cidv2 | `Cidv3) as version ->
let enc =
(* TODO: when dropping support of older compilers, we can change to String.get_uint8 *)
Multibase.Encoding.to_code base |> Bytes.of_string |> fun s ->
Bytes.get_uint8 s 0
in
let ver =
Multicodec.cid_to_code (version :> Multicodec.cid)
|> Multihash.Uvarint.encode
in
let cod = Multicodec.to_code codec |> Multihash.Uvarint.encode in
let has = Md.write hash in
let buf = Cstruct.create 1 in
Cstruct.set_uint8 buf 0 enc;
buf <+> ver <+> cod <+> has

let fail_encoding = function
| Error (`Msg s) -> failwith s
| Error (`Unsupported b) ->
failwith ("Unsupported encoding: " ^ Multibase.Encoding.to_string b)
| Ok v -> v

let to_string t =
let buf = to_cstruct t in
if t.version = `Cidv0 then Cstruct.to_string buf
else
let data = Cstruct.(to_string (sub buf 1 (length buf - 1))) in
Multibase.encode t.base data |> fail_encoding

let of_string s =
if String.length s = 46 && s.[0] = 'Q' && s.[1] = 'm' then
let v =
of_cstruct ~base:`Base58btc
(Cstruct.of_string @@ Multibase.Base58.decode s)
in
let cod = Multicodec.to_code codec |> Multihash.Uvarint.encode in
let has = Md.write hash in
let buf = Cstruct.create 1 in
Cstruct.set_uint8 buf 0 enc;
buf <+> ver <+> cod <+> has

let to_string t =
let buf = to_cstruct t in
if t.version = `Cidv0 then Ok (Cstruct.to_string buf)
else
let data = Cstruct.(to_string (sub buf 1 (length buf - 1))) in
Multibase.encode t.base data

let of_string s =
if String.length s = 46 && s.[0] = 'Q' && s.[1] = 'm' then
let v =
of_cstruct ~base:`Base58btc
(Cstruct.of_string @@ Multibase.Base58.decode s)
in
(v :> (t, [ `Msg of string | `Unsupported of Multibase.Encoding.t ]) result)
else
match Multibase.decode s with
| Ok (base, s) ->
let v = of_cstruct ~base (Cstruct.of_string s) in
(v
:> ( t,
[ `Msg of string | `Unsupported of Multibase.Encoding.t ] )
result)
| Error _ as e -> e

let pp_human ppf { version; base; codec; hash } =
Format.fprintf ppf "%s - %s - %s - %a" (version_string version)
(Multibase.Encoding.to_string base)
(Multicodec.to_string codec)
Md.pp hash
(v
:> (t, [ `Msg of string | `Unsupported of Multibase.Encoding.t ]) result)
else
match Multibase.decode s with
| Ok (base, s) ->
let v = of_cstruct ~base (Cstruct.of_string s) in
(v
:> ( t,
[ `Msg of string | `Unsupported of Multibase.Encoding.t ] )
result)
| Error _ as e -> e

let pp_human ppf { version; base; codec; hash } =
Format.fprintf ppf "%s - %s - %s - %a" (version_string version)
(Multibase.Encoding.to_string base)
(Multicodec.to_string codec)
Md.pp hash
end

include Make (Multihash_digestif)
55 changes: 2 additions & 53 deletions src/cid.mli
Original file line number Diff line number Diff line change
@@ -1,53 +1,2 @@
type version = [ `Cidv0 | `Cidv1 | `Cidv2 | `Cidv3 ]

type t
(** A content-addressed identifier. *)

val v :
version:version ->
base:Multibase.Encoding.t ->
codec:Multicodec.t ->
hash:Cstruct.t Multihash_digestif.t ->
t
(** Build a CID, this performs no checks on any of the inputs *)

val version : t -> version
(** The CID version. *)

val base : t -> Multibase.Encoding.t
(** The multibase encoding of the CID. *)

val codec : t -> Multicodec.t
(** The multicodec type of the data *)

val hash : t -> Cstruct.t Multihash_digestif.t
(** The digest of the CID *)

val equal : t -> t -> bool
(** Tests the equality of two CIDs. *)

val of_string :
string ->
(t, [ `Msg of string | `Unsupported of Multibase.Encoding.t ]) result
(** [of_string s] takes an encoded string [s] that is the CID and
pulls out each of the parts that make it up. *)

val of_cstruct :
base:Multibase.Encoding.t ->
Cstruct.t ->
(t, [ `Msg of string | `Unsupported of Multibase.Encoding.t ]) result
(** [of_cstruct ~base buf] builds a value representing a CID. The buffer
should not be encoded with the multibase encoding. *)

val to_string :
t ->
(string, [ `Msg of string | `Unsupported of Multibase.Encoding.t ]) result
(** [to_string t] converts the CID to a multibase encoded string. Errors happen
if the base encoding is not supported. TODO: change to exception. *)

val to_cstruct : t -> Cstruct.t
(** [to_cstruct t] returns a buffer with the bytes corresponding to the unencoded
CID. *)

val pp_human : Format.formatter -> t -> unit
(** Pretty-prints a CID. *)
include Cid_intf.Intf
(** @inline *)

0 comments on commit 517cd8f

Please sign in to comment.