diff --git a/CHANGES.md b/CHANGES.md index 9b8d0fd..5936475 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,26 @@ +## v4.0.0 + +* Rename the `to/from_bytes` functions to refer to `octets` + instead. This distinguishes the meaning of human-readable + addresses (`string`s in this library) and byte-packed + representations(`octet`s in this library) from the OCaml + `bytes` type that represents mutable strings. + + Porting code should just be a matter of renaming functions + such as: + - `Ipaddr.of_bytes` becomes `Ipaddr.of_octets` + - `Macaddr.to_bytes` becomes `Macaddr.to_octets` + +* Introduce new `write_octets` functions that can write + octet representations of IPv4/v6 into an existing bytestring. + +* Use the `domain-name` library to produce domain names + from IP addresses. + +* Remove the `ipaddr.sexp` and `macaddr.sexp` ocamlfind + subpackages and instead have `ipaddr-sexp` and `macaddr-sexp` + to match the opam package names. + ## v3.1.0 (2019-03-02) * Do not leak a `Not_found` exception when parsing `[:` diff --git a/lib/ipaddr.ml b/lib/ipaddr.ml index dc4ec5d..2eb4dfd 100644 --- a/lib/ipaddr.ml +++ b/lib/ipaddr.ml @@ -62,7 +62,6 @@ let (>!) x y = (x >|> y) &&& 0xFF_l let ( 4 then raise (too_much bs); - if len < 4 then raise (need_more bs); - of_bytes_raw bs 0 - - let of_bytes bs = try_with_result of_bytes_exn bs - - let to_bytes_raw i b o = - Bytes.set b (0 + o) (Char.chr ((|~) (i >! 24))); - Bytes.set b (1 + o) (Char.chr ((|~) (i >! 16))); - Bytes.set b (2 + o) (Char.chr ((|~) (i >! 8))); - Bytes.set b (3 + o) (Char.chr ((|~) (i >! 0))) - - let to_bytes i = - let b = Bytes.create 4 in - to_bytes_raw i b 0; - Bytes.to_string b - - (* Int32*) + (* Octets conversion *) + + let of_octets_exn ?(off=0) bs = + try + make + (Char.code bs.[0 + off]) + (Char.code bs.[1 + off]) + (Char.code bs.[2 + off]) + (Char.code bs.[3 + off]) + with _ -> raise (need_more bs) + + let of_octets ?off bs = try_with_result (of_octets_exn ?off) bs + + let write_octets_exn ?(off=0) i b = + try + Bytes.set b (0 + off) (Char.chr ((|~) (i >! 24))); + Bytes.set b (1 + off) (Char.chr ((|~) (i >! 16))); + Bytes.set b (2 + off) (Char.chr ((|~) (i >! 8))); + Bytes.set b (3 + off) (Char.chr ((|~) (i >! 0))) + with _ -> raise (need_more (Bytes.to_string b)) + + let write_octets ?off i bs = try_with_result (write_octets_exn ?off i) bs + + let to_octets i = + String.init 4 (function + | 0 -> Char.chr ((|~) (i >! 24)) + | 1 -> Char.chr ((|~) (i >! 16)) + | 2 -> Char.chr ((|~) (i >! 8)) + | 3 -> Char.chr ((|~) (i >! 0)) + | _ -> assert false) + + (* Int32 *) let of_int32 i = i let to_int32 i = i @@ -230,7 +232,7 @@ module V4 = struct Bytes.set macb 3 (Char.chr ((|~) (i >|> 16 &&& 0x7F_l))); Bytes.set macb 4 (Char.chr ((|~) (i >! 8))); Bytes.set macb 5 (Char.chr ((|~) (i >! 0))); - Macaddr.of_bytes_exn (Bytes.to_string macb) + Macaddr.of_octets_exn (Bytes.to_string macb) (* Host *) let to_domain_name i = @@ -433,21 +435,11 @@ module B128 = struct in (a,b,c,d,e,f,g,h) - let to_bytes_raw (a,b,c,d) byte o = - V4.to_bytes_raw a byte (o+0); - V4.to_bytes_raw b byte (o+4); - V4.to_bytes_raw c byte (o+8); - V4.to_bytes_raw d byte (o+12) - - let _of_bytes_exn bs = (* TODO : from cstruct *) - let len = String.length bs in - if len > 16 then raise (too_much bs); - if len < 16 then raise (need_more bs); - let hihi = V4.of_bytes_raw bs 0 in - let hilo = V4.of_bytes_raw bs 4 in - let lohi = V4.of_bytes_raw bs 8 in - let lolo = V4.of_bytes_raw bs 12 in - of_int32 (hihi, hilo, lohi, lolo) + let write_octets_exn ?(off=0) (a,b,c,d) byte = + V4.write_octets_exn ~off a byte; + V4.write_octets_exn ~off:(off+4) b byte; + V4.write_octets_exn ~off:(off+8) c byte; + V4.write_octets_exn ~off:(off+12) d byte let compare (a1,b1,c1,d1) (a2,b2,c2,d2) = match V4.compare a1 a2 with @@ -639,25 +631,21 @@ module V6 = struct (* byte conversion *) - let of_bytes_raw bs o = (* TODO : from cstruct *) - let hihi = V4.of_bytes_raw bs (o + 0) in - let hilo = V4.of_bytes_raw bs (o + 4) in - let lohi = V4.of_bytes_raw bs (o + 8) in - let lolo = V4.of_bytes_raw bs (o + 12) in + let of_octets_exn ?(off=0) bs = (* TODO : from cstruct *) + let hihi = V4.of_octets_exn ~off bs in + let hilo = V4.of_octets_exn ~off:(off+4) bs in + let lohi = V4.of_octets_exn ~off:(off+8) bs in + let lolo = V4.of_octets_exn ~off:(off+12) bs in of_int32 (hihi, hilo, lohi, lolo) - let of_bytes_exn bs = (* TODO : from cstruct *) - let len = String.length bs in - if len > 16 then raise (too_much bs); - if len < 16 then raise (need_more bs); - of_bytes_raw bs 0 + let of_octets ?off bs = try_with_result (of_octets_exn ?off) bs - let of_bytes bs = try_with_result of_bytes_exn bs + let write_octets ?off i bs = try_with_result (write_octets_exn ?off i) bs - let to_bytes i = - let bs = Bytes.create 16 in - to_bytes_raw i bs 0; - Bytes.to_string bs + let to_octets i = + let b = Bytes.create 16 in + write_octets_exn i b; + Bytes.to_string b (* MAC *) (* {{:https://tools.ietf.org/html/rfc2464#section-7}RFC 2464}. *) @@ -670,7 +658,7 @@ module V6 = struct Bytes.set macb 3 (Char.chr ((|~) (i >! 16))); Bytes.set macb 4 (Char.chr ((|~) (i >! 8))); Bytes.set macb 5 (Char.chr ((|~) (i >! 0))); - Macaddr.of_bytes_exn (Bytes.to_string macb) + Macaddr.of_octets_exn (Bytes.to_string macb) (* Host *) let to_domain_name (a,b,c,d) = @@ -886,7 +874,7 @@ module V6 = struct let link_address_of_mac = let c b i = Char.code (String.get b i) in fun mac -> - let bmac = Macaddr.to_bytes mac in + let bmac = Macaddr.to_octets mac in let c_0 = c bmac 0 lxor 2 in let addr = make 0 0 0 0 (c_0 lsl 8 + c bmac 1) diff --git a/lib/ipaddr.mli b/lib/ipaddr.mli index d4c5f5e..2d92a45 100644 --- a/lib/ipaddr.mli +++ b/lib/ipaddr.mli @@ -1,4 +1,5 @@ (* + * Copyright (c) 2019 Anil Madhavapeddy * Copyright (c) 2013-2015 David Sheets * * Permission to use, copy, modify, and distribute this software for any @@ -54,20 +55,21 @@ module V4 : sig (** Converts the low bytes of four int values into an abstract {! V4.t }. *) val make : int -> int -> int -> int -> t - (** {3 Text string conversion} *) + (** {3 Text string conversion} + These manipulate human-readable IPv4 addresses (for example [192.168.1.2]). *) - (** [of_string s] is the address {!t} represented by the IPv4 address [s]. - Returns a human-readable error string if parsing failed. *) + (** [of_string s] is the address {!t} represented by the human-readable IPv4 + address [s]. Returns a human-readable error string if parsing failed. *) val of_string : string -> (t, [> `Msg of string ]) result - (** [of_string_exn s] is the address {!t} represented - by [s]. Raises {!Parse_error} if [s] is not a - valid representation of an IPv4 address. *) + (** [of_string_exn s] is the address {!t} represented as a human-readable IPv4 + address [s]. Raises {!Parse_error} if [s] is not exactly 4 bytes long. *) val of_string_exn : string -> t (** [of_string_raw s off] acts as {!of_string_exn} but takes as an extra argument the offset into the string for reading. [off] will be - mutated to an unspecified value during the function call. *) + mutated to an unspecified value during the function call. [s] will + a {!Parse_error} exception if it is an invalid or truncated IP address. *) val of_string_raw : string -> int ref -> t (** [to_string ipv4] is the dotted decimal string representation @@ -82,28 +84,36 @@ module V4 : sig the formatter [f]. *) val pp : Format.formatter -> t -> unit [@@ocaml.toplevel_printer] - (** {3 Bytestring conversion} *) - - (** [of_bytes s] is the address {!t} represented by the IPv4 octets - represented by [s]. [s] should be exactly 4 bytes long. - Returns a human-readable error string if parsing fails. *) - val of_bytes : string -> (t, [> `Msg of string ]) result - - (** [of_bytes_exn ipv4_octets] is the address represented - by [ipv4_octets]. Raises [Parse_error] if [ipv4_octets] is not a - valid representation of an IPv4 address. *) - val of_bytes_exn : string -> t - - (** [of_bytes_raw s off] is the same as {!of_bytes_exn} but takes - an extra paramenter [off] the offset into the bytes for reading. *) - val of_bytes_raw : string -> int -> t - - (** [to_bytes ipv4] is a string of length 4 encoding [ipv4]. *) - val to_bytes : t -> string - - (** [to_bytes_raw ipv4 bytes offset] writes the 4 byte encoding of [ipv4] - into [bytes] at offset [offset]. *) - val to_bytes_raw : t -> Bytes.t -> int -> unit + (** {3 Octets conversion} + These manipulate IPv4 addresses represented as a sequence of + four bytes. (e.g for example [0xc0a80102] will be the representation + of the human-readable [192.168.1.2] address. *) + + (** [of_octets ?off s] is the address {!t} represented by the IPv4 octets + represented by [s] starting from offset [off] within the string. + [s] must be at least [off+4] bytes long. + Returns a human-readable error string if parsing fails. + [off] defaults to 0. *) + val of_octets : ?off:int -> string -> (t, [> `Msg of string ]) result + + (** [of_octets_exn ipv4_octets] is the IPv4 address represented + by [ipv4_octets] starting from offset [off] within the string. + Raises {!Parse_error} if [ipv4_octets] is not at least [off+4] bytes long. + [off] defaults to 0. *) + val of_octets_exn : ?off:int -> string -> t + + (** [write_octets ?off ipv4 b] writes the [ipv4] as octets to [b] starting + from offset [off]. [b] must be at least [off+4] long or an error is + returned. *) + val write_octets : ?off:int -> t -> bytes -> (unit, [> `Msg of string ]) result + + (** [write_octets_exn ?off ipv4 b] writes the [ipv4] as octets to [b] starting + from offset [off]. [b] must be at least [off+4] long or a + {!Parse_error} is raised. *) + val write_octets_exn : ?off:int -> t -> bytes -> unit + + (** [to_octets ipv4] returns the 4 bytes representing the [ipv4] octets. *) + val to_octets : t -> string (** {3 Int conversion} *) @@ -200,7 +210,7 @@ module V4 : sig val pp : Format.formatter -> t -> unit [@@ocaml.toplevel_printer] (** [of_address_string_exn cidr_addr] is the address and prefix - represented by [cidr_addr]. Raises [Parse_error] if [cidr_addr] is not + represented by [cidr_addr]. Raises {!Parse_error} if [cidr_addr] is not a valid representation of a CIDR-scoped address. *) val of_address_string_exn : string -> t * addr @@ -310,7 +320,7 @@ module V6 : sig (** {3 Text string conversion} *) (** [of_string_exn ipv6_string] is the address represented - by [ipv6_string]. Raises [Parse_error] if [ipv6_string] is not a + by [ipv6_string]. Raises {!Parse_error} if [ipv6_string] is not a valid representation of an IPv6 address. *) val of_string_exn : string -> t @@ -323,7 +333,7 @@ module V6 : sig val of_string_raw : string -> int ref -> t (** [to_string ipv6] is the string representation of [ipv6], - i.e. XXX:XX:X::XXX:XX. *) + i.e. [XXX:XX:X::XXX:XX]. *) val to_string : t -> string (** [to_buffer buf ipv6] writes the string representation of [ipv6] into the @@ -334,27 +344,30 @@ module V6 : sig the formatter [f]. *) val pp : Format.formatter -> t -> unit [@@ocaml.toplevel_printer] - (** {3 Bytestring conversion} *) + (** {3 Octets conversion} *) - (** [of_bytes_exn ipv6_octets] is the address represented - by [ipv6_octets]. Raises [Parse_error] if [ipv6_octets] is not a - valid representation of an IPv6 address. *) - val of_bytes_exn : string -> t + (** [of_octets_exn ?off ipv6_octets] is the address represented + by [ipv6_octets], starting from offset [off]. + Raises {!Parse_error} if [ipv6_octets] is not a valid representation of + an IPv6 address. *) + val of_octets_exn : ?off:int -> string -> t - (** Same as {!of_bytes_exn} but returns an result type instead of raising + (** Same as {!of_octets_exn} but returns an result type instead of raising an exception. *) - val of_bytes : string -> (t, [> `Msg of string ]) result + val of_octets : ?off:int -> string -> (t, [> `Msg of string ]) result - (** Same as {!of_bytes_exn} but takes an extra paramenter, the offset into - the bytes for reading. *) - val of_bytes_raw : string -> int -> t + (** [write_octets_exn ?off ipv6 b] writes 16 bytes that encode [ipv6] into [b] starting + from offset [off] within [b]. [b] must be at least [off+16] bytes long or + a {!Parse_error} exception will be raised. *) + val write_octets_exn : ?off:int -> t -> bytes -> unit - (** [to_bytes ipv6] is a string of length 16 encoding [ipv6]. *) - val to_bytes : t -> string - - (** [to_bytes_raw ipv6 bytes offset] writes the 16 bytes encoding of [ipv6] - into [bytes] at offset [offset]. *) - val to_bytes_raw : t -> Bytes.t -> int -> unit + (** [write_octets ?off ipv6 b] writes 16 bytes that encode [ipv6] into [b] starting + from offset [off] within [b]. [b] must be at least [off+16] bytes long or + an error is returned. *) + val write_octets : ?off:int -> t -> bytes -> (unit, [> `Msg of string ]) result + + (** [to_octets ipv6] returns the 16 bytes representing the [ipv6] octets. *) + val to_octets : t -> string (** {3 Int conversion} *) @@ -436,7 +449,7 @@ module V6 : sig val network_address : t -> addr -> addr (** [of_string_exn cidr] is the subnet prefix represented by the CIDR - string, [cidr]. Raises [Parse_error] if [cidr] is not a valid + string, [cidr]. Raises {!Parse_error} if [cidr] is not a valid representation of a CIDR notation routing prefix. *) val of_string_exn : string -> t @@ -457,7 +470,7 @@ module V6 : sig val pp : Format.formatter -> t -> unit [@@ocaml.toplevel_printer] (** [of_address_string_exn cidr_addr] is the address and prefix - represented by [cidr_addr]. Raises [Parse_error] if [cidr_addr] is not + represented by [cidr_addr]. Raises {!Parse_error} if [cidr_addr] is not a valid representation of a CIDR-scoped address. *) val of_address_string_exn : string -> t * addr @@ -642,7 +655,7 @@ module Prefix : sig val pp : Format.formatter -> t -> unit [@@ocaml.toplevel_printer] (** [of_string_exn cidr] is the subnet prefix represented by the CIDR - string, [cidr]. Raises [Parse_error] if [cidr] is not a valid + string, [cidr]. Raises {!Parse_error} if [cidr] is not a valid representation of a CIDR notation routing prefix. *) val of_string_exn : string -> t diff --git a/lib/macaddr.ml b/lib/macaddr.ml index d656af8..3c66a79 100644 --- a/lib/macaddr.ml +++ b/lib/macaddr.ml @@ -28,12 +28,12 @@ type t = Bytes.t (* length 6 only *) let compare = Bytes.compare (* Raw MAC address off the wire (network endian) *) -let of_bytes_exn x = +let of_octets_exn x = if String.length x <> 6 then raise (Parse_error ("MAC is exactly 6 bytes", x)) else Bytes.of_string x -let of_bytes x = try_with_result of_bytes_exn x +let of_octets x = try_with_result of_octets_exn x let int_of_hex_char c = let c = int_of_char (Char.uppercase_ascii c) - 48 in @@ -109,7 +109,7 @@ let to_string ?(sep=':') x = (chri x 4) sep (chri x 5) -let to_bytes x = Bytes.to_string x +let to_octets x = Bytes.to_string x let pp ppf i = Format.fprintf ppf "%s" (to_string i) diff --git a/lib/macaddr.mli b/lib/macaddr.mli index a951086..1db34fb 100644 --- a/lib/macaddr.mli +++ b/lib/macaddr.mli @@ -26,27 +26,28 @@ exception Parse_error of string * string (** Type of the hardware address (MAC) of an ethernet interface. *) type t -(** {2 Functions converting MAC addresses to/from bytes/string} *) +(** {2 Functions converting MAC addresses to/from octets/string} *) -(** [of_bytes_exn buf] is the hardware address extracted from +(** [of_octets_exn buf] is the hardware address extracted from [buf]. Raises [Parse_error] if [buf] has not length 6. *) -val of_bytes_exn : string -> t +val of_octets_exn : string -> t -(** Same as above but returns an option type instead of raising an - exception. *) -val of_bytes : string -> (t, [> `Msg of string]) result +(** Same as {!of_octets_exn} but returns a result type instead of + raising an exception. *) +val of_octets : string -> (t, [> `Msg of string]) result -(** [of_string_exn mac_string] is the hardware address represented by - [mac_string]. Raises [Parse_error] if [mac_string] is not a +(** [of_string_exn mac_string] is the human-readable hardware address represented by + [mac_string]. Raises {!Parse_error} if [mac_string] is not a valid representation of a MAC address. *) val of_string_exn : string -> t -(** Same as above but returns an option type instead of raising an +(** Same as {!of_string_exn} but returns a result type instead of raising an exception. *) val of_string : string -> (t, [> `Msg of string]) result -(** [to_bytes mac_addr] is a string of size 6 encoding [mac_addr]. *) -val to_bytes : t -> string +(** [to_octets mac_addr] is a string of size 6 encoding [mac_addr] as a + sequence of bytes. *) +val to_octets : t -> string (** [to_string ?(sep=':') mac_addr] is the [sep]-separated string representation of [mac_addr], i.e. [xx:xx:xx:xx:xx:xx]. *) diff --git a/lib_test/test_ipaddr.ml b/lib_test/test_ipaddr.ml index c5ad005..ddc46c1 100644 --- a/lib_test/test_ipaddr.ml +++ b/lib_test/test_ipaddr.ml @@ -20,7 +20,6 @@ open Ipaddr let error s msg = s, Parse_error (msg,s) let need_more s = error s "not enough data" -let too_much s = error s "too much data" let bad_char i s = error s (Printf.sprintf "invalid character '%c' at %d" s.[i] i) @@ -89,16 +88,15 @@ module Test_v4 = struct let test_bytes_rt () = let addr = "\254\099\003\128" in assert_equal ~msg:(String.escaped addr) - V4.(to_bytes (of_bytes_exn addr)) addr + V4.(to_octets (of_octets_exn addr)) addr let test_bytes_rt_bad () = let addrs = [ need_more "\254\099\003"; - too_much "\254\099\003\128\001"; ] in List.iter (fun (addr,exn) -> assert_raises ~msg:(String.escaped addr) exn - (fun () -> V4.of_bytes_exn addr) + (fun () -> V4.of_octets_exn addr) ) addrs let test_int32_rt () = @@ -273,7 +271,7 @@ module Test_v4 = struct assert_equal ~msg:"localhost" true V4.(Prefix.(mem localhost loopback)) let test_multicast_mac () = - let ip = V4.of_bytes_exn "\xff\xbf\x9f\x8f" in + let ip = V4.of_octets_exn "\xff\xbf\x9f\x8f" in let multicast = V4.Prefix.(network_address multicast ip) in let unicast_mac_str = Macaddr.to_string (V4.multicast_to_mac ip) in let multicast_mac_str = Macaddr.to_string (V4.multicast_to_mac multicast) in @@ -405,18 +403,16 @@ module Test_v6 = struct let addr = "\000\000\000\000\000\000\000\000\000\000\255\255\192\168\000\001" in - let v6 = V6.of_bytes_exn addr in - assert_equal ~msg:(String.escaped addr) V6.(to_bytes v6) addr + let v6 = V6.of_octets_exn addr in + assert_equal ~msg:(String.escaped addr) V6.(to_octets v6) addr let test_bytes_rt_bad () = let addrs = [ need_more "\000\000\000\000\000\000\000\000\000\000\255\255\192\168\001"; - too_much - "\000\000\000\000\000\000\000\000\000\000\255\255\192\168\000\000\001"; ] in List.iter (fun (addr,exn) -> assert_raises ~msg:(String.escaped addr) exn - (fun () -> V6.of_bytes_exn addr) + (fun () -> V6.of_octets_exn addr) ) addrs let test_int32_rt () = diff --git a/lib_test/test_macaddr.ml b/lib_test/test_macaddr.ml index 678fc3c..d9c8822 100644 --- a/lib_test/test_macaddr.ml +++ b/lib_test/test_macaddr.ml @@ -50,7 +50,7 @@ let test_string_rt_bad () = let test_bytes_rt () = let addr = "\254\099\003\128\000\000" in - assert_equal ~msg:(String.escaped addr) (to_bytes (of_bytes_exn addr)) addr + assert_equal ~msg:(String.escaped addr) (to_octets (of_octets_exn addr)) addr let test_bytes_rt_bad () = let addrs = [ @@ -58,7 +58,7 @@ let test_bytes_rt_bad () = "\254\099\003\128\000\000\233"; ] in List.iter (fun addr -> - assert_result_failure ~msg:(String.escaped addr) (of_bytes addr)) addrs + assert_result_failure ~msg:(String.escaped addr) (of_octets addr)) addrs let test_make_local () = let () = Random.self_init () in @@ -66,10 +66,10 @@ let test_make_local () = let local_addr = make_local bytegen in assert_equal ~msg:"is_local" (is_local local_addr) true; assert_equal ~msg:"is_unicast" (is_unicast local_addr) true; - assert_equal ~msg:"localize" (to_bytes local_addr).[0] (Char.chr 254); + assert_equal ~msg:"localize" (to_octets local_addr).[0] (Char.chr 254); for i=1 to 5 do assert_equal ~msg:("addr.["^(string_of_int i)^"]") - (to_bytes local_addr).[i] (Char.chr (bytegen i)) + (to_octets local_addr).[i] (Char.chr (bytegen i)) done; assert_equal ~msg:"get_oui" (get_oui local_addr) ((254 lsl 16) + (254 lsl 8) + 253)