From c303536b682421b03b47c5d77f97204d3a8137ab Mon Sep 17 00:00:00 2001 From: Anil Madhavapeddy Date: Thu, 11 Jul 2019 09:34:17 +0100 Subject: [PATCH] Add new `Ipaddr_cstruct` and `Macaddr_cstruct` libraries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit for conversion to/from cstructs based on #36 Co-authored-by: Nicolás Ojeda Bär --- .travis.yml | 2 +- CHANGES.md | 3 ++ dune-project | 3 +- ipaddr-cstruct.opam | 24 +++++++++++++ ipaddr-sexp.opam | 3 +- lib/dune | 12 +++++++ lib/ipaddr_cstruct.ml | 76 ++++++++++++++++++++++++++++++++++++++++ lib/ipaddr_cstruct.mli | 61 ++++++++++++++++++++++++++++++++ lib/macaddr_cstruct.ml | 40 +++++++++++++++++++++ lib/macaddr_cstruct.mli | 36 +++++++++++++++++++ lib_test/dune | 10 +++--- lib_test/test_ipaddr.ml | 34 ++++++++++++++++++ lib_test/test_macaddr.ml | 19 ++++++++++ macaddr-cstruct.opam | 24 +++++++++++++ macaddr-sexp.opam | 3 +- 15 files changed, 341 insertions(+), 9 deletions(-) create mode 100644 ipaddr-cstruct.opam create mode 100644 lib/ipaddr_cstruct.ml create mode 100644 lib/ipaddr_cstruct.mli create mode 100644 lib/macaddr_cstruct.ml create mode 100644 lib/macaddr_cstruct.mli create mode 100644 macaddr-cstruct.opam diff --git a/.travis.yml b/.travis.yml index 4450401..dbf05d3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ script: bash ./.travis-docker.sh env: global: - PACKAGE="ipaddr" - - PINS="ipaddr:. macaddr:." + - PINS="ipaddr:. macaddr:. ipaddr-sexp:. macaddr-sexp:. ipaddr-cstruct:. macaddr-cstruct:." matrix: - DISTRO=debian-stable OCAML_VERSION=4.04 - DISTRO=ubuntu OCAML_VERSION=4.05 diff --git a/CHANGES.md b/CHANGES.md index 5936475..89332c9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -21,6 +21,9 @@ subpackages and instead have `ipaddr-sexp` and `macaddr-sexp` to match the opam package names. +* Add new `Ipaddr_cstruct` and `Macaddr_cstruct` libraries + for conversion to/from cstructs (#36 @nojb @avsm) + ## v3.1.0 (2019-03-02) * Do not leak a `Not_found` exception when parsing `[:` diff --git a/dune-project b/dune-project index de8d3c1..a0d3cf5 100644 --- a/dune-project +++ b/dune-project @@ -1,2 +1,3 @@ -(lang dune 1.0) +(lang dune 1.9) (name ipaddr) +(allow_approximate_merlin) diff --git a/ipaddr-cstruct.opam b/ipaddr-cstruct.opam new file mode 100644 index 0000000..121ed97 --- /dev/null +++ b/ipaddr-cstruct.opam @@ -0,0 +1,24 @@ +opam-version: "2.0" +maintainer: "anil@recoil.org" +authors: ["David Sheets" "Anil Madhavapeddy" "Hugo Heuzard"] +synopsis: "A library for manipulation of IP address representations using Cstructs" +license: "ISC" +tags: ["org:mirage" "org:xapi-project"] +homepage: "https://github.com/mirage/ocaml-ipaddr" +doc: "https://mirage.github.io/ocaml-ipaddr/" +bug-reports: "https://github.com/mirage/ocaml-ipaddr/issues" +depends: [ + "ocaml" {>= "4.04.0"} + "dune" {build} + "ipaddr" + "cstruct" +] +build: [ + ["dune" "subst"] {pinned} + ["dune" "build" "-p" name "-j" jobs] + ["dune" "runtest" "-p" name "-j" jobs] {with-test} +] +dev-repo: "git+https://github.com/mirage/ocaml-ipaddr.git" +description: """ +Cstruct convertions for macaddr +""" diff --git a/ipaddr-sexp.opam b/ipaddr-sexp.opam index 502a678..9e8a0c0 100644 --- a/ipaddr-sexp.opam +++ b/ipaddr-sexp.opam @@ -1,7 +1,7 @@ opam-version: "2.0" maintainer: "anil@recoil.org" authors: ["David Sheets" "Anil Madhavapeddy" "Hugo Heuzard"] -synopsis: "A library for manipulation of IP (and MAC) address representations" +synopsis: "A library for manipulation of IP address representations usnig sexp" description: """ Sexp convertions for ipaddr """ @@ -15,6 +15,7 @@ depends: [ "ocaml" {>= "4.04.0"} "dune" {build} "ipaddr" + "ipaddr-cstruct" {with-test} "ounit" {with-test} "ppx_sexp_conv" {>= "v0.9.0"} ] diff --git a/lib/dune b/lib/dune index 1a82853..3540099 100644 --- a/lib/dune +++ b/lib/dune @@ -29,6 +29,18 @@ (modules ipaddr_unix) (libraries unix ipaddr)) +(library + (name ipaddr_cstruct) + (public_name ipaddr-cstruct) + (modules ipaddr_cstruct) + (libraries ipaddr cstruct)) + +(library + (name macaddr_cstruct) + (public_name macaddr-cstruct) + (modules macaddr_cstruct) + (libraries macaddr cstruct)) + (library (name ipaddr_top) (public_name ipaddr.top) diff --git a/lib/ipaddr_cstruct.ml b/lib/ipaddr_cstruct.ml new file mode 100644 index 0000000..803dcbd --- /dev/null +++ b/lib/ipaddr_cstruct.ml @@ -0,0 +1,76 @@ +(* + * Copyright (c) 2019 Anil Madhavapeddy + * Copyright (c) 2014 Nicolás Ojeda Bär + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + *) + +let need_more x = Ipaddr.Parse_error ("not enough data", x) + +let try_with_result fn a = + try Ok (fn a) + with Ipaddr.Parse_error (msg, _) -> Error (`Msg ("Ipaddr: " ^ msg)) + +module V4 = struct + + let of_cstruct_exn cs = + let len = Cstruct.len cs in + if len < 4 then raise (need_more (Cstruct.to_string cs)); + Ipaddr.V4.of_int32 (Cstruct.BE.get_uint32 cs 0) + + let of_cstruct cs = + try_with_result of_cstruct_exn cs + + let write_cstruct_exn i cs = + let len = Cstruct.len cs in + if len < 4 then raise (need_more (Cstruct.to_string cs)); + Cstruct.BE.set_uint32 cs 0 (Ipaddr.V4.to_int32 i) + + let to_cstruct ?(allocator = Cstruct.create) i = + let cs = allocator 4 in + write_cstruct_exn i cs; + cs + +end + +module V6 = struct + + open Ipaddr.V6 + + let of_cstruct_exn cs = + let len = Cstruct.len cs in + if len < 16 then raise (need_more (Cstruct.to_string cs)); + let hihi = Cstruct.BE.get_uint32 cs 0 in + let hilo = Cstruct.BE.get_uint32 cs 4 in + let lohi = Cstruct.BE.get_uint32 cs 8 in + let lolo = Cstruct.BE.get_uint32 cs 12 in + of_int32 (hihi, hilo, lohi, lolo) + + let of_cstruct cs = + try_with_result of_cstruct_exn cs + + let write_cstruct_exn i cs = + let len = Cstruct.len cs in + if len < 16 then raise (need_more (Cstruct.to_string cs)); + let a, b, c, d = to_int32 i in + Cstruct.BE.set_uint32 cs 0 a; + Cstruct.BE.set_uint32 cs 4 b; + Cstruct.BE.set_uint32 cs 8 c; + Cstruct.BE.set_uint32 cs 12 d + + let to_cstruct ?(allocator = Cstruct.create) i = + let cs = allocator 16 in + write_cstruct_exn i cs; + cs +end diff --git a/lib/ipaddr_cstruct.mli b/lib/ipaddr_cstruct.mli new file mode 100644 index 0000000..3f6d4d0 --- /dev/null +++ b/lib/ipaddr_cstruct.mli @@ -0,0 +1,61 @@ +(* + * Copyright (c) 2019 Anil Madhavapeddy + * Copyright (c) 2014 Nicolás Ojeda Bär + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + *) + +(** Convert to and from Cstructs and IP addresses *) + +(** Ipv4 address conversions *) +module V4 : sig + + (** [of_cstruct c] parses the first 4 octets of [c] into an IPv4 address. *) + val of_cstruct : Cstruct.t -> (Ipaddr.V4.t, [> `Msg of string ]) result + + (** [of_cstruct_exn] parses the first 4 octets of [c] into an IPv4 address. + Raises {!Ipaddr.Parse_failure} on error. *) + val of_cstruct_exn : Cstruct.t -> Ipaddr.V4.t + + (** [to_cstruct ipv4] is a cstruct of length 4 encoding [ipv4]. + The cstruct is allocated using [allocator]. If [allocator] is + not provided, [Cstruct.create] is used. *) + val to_cstruct: ?allocator:(int -> Cstruct.t) -> Ipaddr.V4.t -> Cstruct.t + + (** [write_cstruct_exn ipv4 cs] writes 4 bytes into [cs] representing + the [ipv4] address octets. Raises {!Ipaddr.Parse_error} if [cs] + is not at least 4 bytes long. *) + val write_cstruct_exn : Ipaddr.V4.t -> Cstruct.t -> unit +end + +(** Ipv6 address conversions *) +module V6 : sig + + (** [of_cstruct c] parses the first 16 octets of [c] into an IPv6 address. *) + val of_cstruct : Cstruct.t -> (Ipaddr.V6.t, [> `Msg of string ]) result + + (** [of_cstruct_exn] parses the first 16 octets of [c] into an IPv6 address. + Raises {!Ipaddr.Parse_failure} on error. *) + val of_cstruct_exn : Cstruct.t -> Ipaddr.V6.t + + (** [to_cstruct ipv6] is a cstruct of length 16 encoding [ipv6]. + The cstruct is allocated using [allocator]. If [allocator] is + not provided, [Cstruct.create] is used. *) + val to_cstruct: ?allocator:(int -> Cstruct.t) -> Ipaddr.V6.t -> Cstruct.t + + (** [write_cstruct_exn ipv6 cs] writes 16 bytes into [cs] representing + the [ipv6] address octets. Raises {!Ipaddr.Parse_error} if [cs] + is not at least 16 bytes long. *) + val write_cstruct_exn : Ipaddr.V6.t -> Cstruct.t -> unit +end diff --git a/lib/macaddr_cstruct.ml b/lib/macaddr_cstruct.ml new file mode 100644 index 0000000..4d9367b --- /dev/null +++ b/lib/macaddr_cstruct.ml @@ -0,0 +1,40 @@ +(* + * Copyright (c) 2019 Anil Madhavapeddy + * Copyright (c) 2014 Nicolás Ojeda Bär + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + *) + +let try_with_result fn a = + try Ok (fn a) + with Macaddr.Parse_error (msg, _) -> Error (`Msg ("Macaddr: " ^ msg)) + +let of_cstruct_exn cs = + if Cstruct.len cs <> 6 + then raise (Macaddr.Parse_error ("MAC is exactly 6 bytes", Cstruct.to_string cs)) + else Cstruct.to_string cs |> Macaddr.of_octets_exn + +let of_cstruct cs = + try_with_result of_cstruct_exn cs + +let write_cstruct_exn (mac:Macaddr.t) cs = + let len = Cstruct.len cs in + let mac = Macaddr.to_octets mac in + if len <> 6 then raise (Macaddr.Parse_error ("MAC is exactly 6 bytes", mac)); + Cstruct.blit_from_string mac 0 cs 0 6 + +let to_cstruct ?(allocator = Cstruct.create) mac = + let cs = allocator 6 in + write_cstruct_exn mac cs; + cs diff --git a/lib/macaddr_cstruct.mli b/lib/macaddr_cstruct.mli new file mode 100644 index 0000000..f272ee2 --- /dev/null +++ b/lib/macaddr_cstruct.mli @@ -0,0 +1,36 @@ +(* + * Copyright (c) 2019 Anil Madhavapeddy + * Copyright (c) 2014 Nicolás Ojeda Bär + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + *) + +(** Convert to and from Cstructs and MAC address. *) + +(** [of_cstruct c] parses the 6 octets of [c] into a MAC address. *) +val of_cstruct : Cstruct.t -> (Macaddr.t, [> `Msg of string ]) result + +(** [of_cstruct_exn] parses the 6 octets of [c] into a MAC address. + Raises {!Macaddr.Parse_failure} on error. *) +val of_cstruct_exn : Cstruct.t -> Macaddr.t + +(** [to_cstruct mac] is a cstruct of length 4 encoding [ipv4]. + The cstruct is allocated using [allocator]. If [allocator] is + not provided, [Cstruct.create] is used. *) +val to_cstruct: ?allocator:(int -> Cstruct.t) -> Macaddr.t -> Cstruct.t + +(** [write_cstruct_exn mac cs] writes 6 bytes into [cs] representing + the [mac] address octets. Raises {!Macaddr.Parse_error} if [cs] + is not 6 bytes long. *) +val write_cstruct_exn : Macaddr.t -> Cstruct.t -> unit diff --git a/lib_test/dune b/lib_test/dune index 93ed080..07f8c2a 100644 --- a/lib_test/dune +++ b/lib_test/dune @@ -17,18 +17,18 @@ (test (name test_ipaddr) - (package ipaddr) + (package ipaddr-sexp) (modules test_ipaddr) - (libraries ipaddr test_ipaddr_sexp oUnit)) + (libraries ipaddr ipaddr-cstruct test_ipaddr_sexp oUnit)) (test (name test_macaddr) - (package macaddr) + (package macaddr-sexp) (modules test_macaddr) - (libraries macaddr test_macaddr_sexp oUnit)) + (libraries macaddr macaddr-cstruct test_macaddr_sexp oUnit)) (test (name test_ppx) (modules test_ppx) - (package ipaddr) + (package ipaddr-sexp) (libraries ipaddr macaddr test_ipaddr_sexp test_macaddr_sexp oUnit)) diff --git a/lib_test/test_ipaddr.ml b/lib_test/test_ipaddr.ml index ddc46c1..01d14c2 100644 --- a/lib_test/test_ipaddr.ml +++ b/lib_test/test_ipaddr.ml @@ -290,6 +290,20 @@ module Test_v4 = struct (V4.to_domain_name ip) name ; assert_equal ~msg:"of_domain_name" (V4.of_domain_name name) (Some ip) + let test_cstruct_rt () = + let addr = "\254\099\003\128" in + assert_equal ~msg:(String.escaped addr) + (Cstruct.to_string Ipaddr_cstruct.V4.(to_cstruct (of_cstruct_exn (Cstruct.of_string addr)))) addr + + let test_cstruct_rt_bad () = + let addrs = [ + need_more "\254\099\003"; + ] in + List.iter (fun (addr,exn) -> + assert_raises ~msg:(String.escaped addr) exn + (fun () -> Ipaddr_cstruct.V4.of_cstruct_exn (Cstruct.of_string addr)) + ) addrs + let suite = "Test V4" >::: [ "string_rt" >:: test_string_rt; "string_rt_bad" >:: test_string_rt_bad; @@ -297,6 +311,8 @@ module Test_v4 = struct "string_raw_rt_bad" >:: test_string_raw_rt_bad; "bytes_rt" >:: test_bytes_rt; "bytes_rt_bad" >:: test_bytes_rt_bad; + "cstruct_rt" >:: test_cstruct_rt; + "cstruct_rt_bad" >:: test_cstruct_rt_bad; "int32_rt" >:: test_int32_rt; "prefix_string_rt" >:: test_prefix_string_rt; "prefix_string_rt_bad" >:: test_prefix_string_rt_bad; @@ -415,6 +431,22 @@ module Test_v6 = struct (fun () -> V6.of_octets_exn addr) ) addrs + let test_cstruct_rt () = + let addr = + "\000\000\000\000\000\000\000\000\000\000\255\255\192\168\000\001" + in + let v6 = Ipaddr_cstruct.V6.of_cstruct_exn (Cstruct.of_string addr) in + assert_equal ~msg:(String.escaped addr) (Cstruct.to_string Ipaddr_cstruct.V6.(to_cstruct v6)) addr + + let test_cstruct_rt_bad () = + let addrs = [ + need_more "\000\000\000\000\000\000\000\000\000\000\255\255\192\168\001"; + ] in + List.iter (fun (addr,exn) -> + assert_raises ~msg:(String.escaped addr) exn + (fun () -> Ipaddr_cstruct.V6.of_cstruct_exn (Cstruct.of_string addr)) + ) addrs + let test_int32_rt () = let (a,b,c,d) as addr = 0x2001_0665_l, 0x0000_0000_l, 0xff00_00ff_l, 0xfe00_0001_l @@ -622,6 +654,8 @@ module Test_v6 = struct "string_raw_rt_bad" >:: test_string_raw_rt_bad; "bytes_rt" >:: test_bytes_rt; "bytes_rt_bad" >:: test_bytes_rt_bad; + "cstruct_rt" >:: test_cstruct_rt; + "cstruct_rt_bad" >:: test_cstruct_rt_bad; "int32_rt" >:: test_int32_rt; "prefix_string_rt" >:: test_prefix_string_rt; "prefix_string_rt_bad" >:: test_prefix_string_rt_bad; diff --git a/lib_test/test_macaddr.ml b/lib_test/test_macaddr.ml index d9c8822..f80c872 100644 --- a/lib_test/test_macaddr.ml +++ b/lib_test/test_macaddr.ml @@ -60,6 +60,23 @@ let test_bytes_rt_bad () = List.iter (fun addr -> assert_result_failure ~msg:(String.escaped addr) (of_octets addr)) addrs +let test_cstruct_rt () = + let open Macaddr_cstruct in + let addr = "\254\099\003\128\000\000" in + assert_equal ~msg:(String.escaped addr) + (Cstruct.to_string (to_cstruct (of_cstruct_exn (Cstruct.of_string addr)))) addr + +let error s = s, Parse_error ("MAC is exactly 6 bytes",s) + +let test_cstruct_rt_bad () = + let open Macaddr_cstruct in + let addrs = [ + error "\254\099\003\128\000"; + error "\254\099\003\128\000\000\233"; + ] in + List.iter (fun (addr,exn) -> + assert_raises ~msg:(String.escaped addr) exn (fun () -> of_cstruct_exn (Cstruct.of_string addr))) addrs + let test_make_local () = let () = Random.self_init () in let bytegen i = if i = 0 then 253 else 255 - i in @@ -79,6 +96,8 @@ let suite = "Test" >::: [ "string_rt_bad" >:: test_string_rt_bad; "bytes_rt" >:: test_bytes_rt; "bytes_rt_bad" >:: test_bytes_rt_bad; + "cstruct_rt" >:: test_cstruct_rt; + "cstruct_rt_bad" >:: test_cstruct_rt_bad; "make_local" >:: test_make_local; ] ;; diff --git a/macaddr-cstruct.opam b/macaddr-cstruct.opam new file mode 100644 index 0000000..ede1f45 --- /dev/null +++ b/macaddr-cstruct.opam @@ -0,0 +1,24 @@ +opam-version: "2.0" +maintainer: "anil@recoil.org" +authors: ["David Sheets" "Anil Madhavapeddy" "Hugo Heuzard"] +synopsis: "A library for manipulation of MAC address representations using Cstructs" +license: "ISC" +tags: ["org:mirage" "org:xapi-project"] +homepage: "https://github.com/mirage/ocaml-ipaddr" +doc: "https://mirage.github.io/ocaml-ipaddr/" +bug-reports: "https://github.com/mirage/ocaml-ipaddr/issues" +depends: [ + "ocaml" {>= "4.04.0"} + "dune" {build} + "macaddr" + "cstruct" +] +build: [ + ["dune" "subst"] {pinned} + ["dune" "build" "-p" name "-j" jobs] + ["dune" "runtest" "-p" name "-j" jobs] {with-test} +] +dev-repo: "git+https://github.com/mirage/ocaml-ipaddr.git" +description: """ +Cstruct convertions for macaddr +""" diff --git a/macaddr-sexp.opam b/macaddr-sexp.opam index 4931cfc..76836a6 100644 --- a/macaddr-sexp.opam +++ b/macaddr-sexp.opam @@ -1,7 +1,7 @@ opam-version: "2.0" maintainer: "anil@recoil.org" authors: ["David Sheets" "Anil Madhavapeddy" "Hugo Heuzard"] -synopsis: "A library for manipulation of MAC address representations" +synopsis: "A library for manipulation of MAC address representations using sexp" license: "ISC" tags: ["org:mirage" "org:xapi-project"] homepage: "https://github.com/mirage/ocaml-ipaddr" @@ -11,6 +11,7 @@ depends: [ "ocaml" {>= "4.04.0"} "dune" {build} "macaddr" + "macaddr-cstruct" {with-test} "ounit" {with-test} "ppx_sexp_conv" {>= "v0.9.0"} ]