(*
 * Copyright (c) 2013-2014 David Sheets <sheets@alum.mit.edu>
 *
 * 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.
 *
 *)

open OUnit
open Ipaddr

let error s msg = (s, Parse_error (msg, s))
let need_more s = error s "not enough data"

let bad_char i s =
  error s (Printf.sprintf "invalid character '%c' at %d" s.[i] i)

let string_of_list f l = "[" ^ (List.map f l |> String.concat "; ") ^ "]"
let ( >>= ) v f = match v with Ok v -> f v | Error _ as e -> e

let assert_raises ~msg exn test_fn =
  assert_raises ~msg exn (fun () ->
      try test_fn ()
      with rtexn ->
        if exn <> rtexn then (
          Printf.eprintf "Stacktrace for '%s':\n%!" msg;
          Printexc.print_backtrace stderr);
        raise rtexn)

module Test_v4 = struct
  let test_string_rt () =
    let addrs = [ ("192.168.0.1", "192.168.0.1") ] in
    List.iter
      (fun (addr, rt) ->
        let os = V4.of_string_exn addr in
        let ts = V4.to_string os in
        assert_equal ~msg:addr ts rt;
        let os = Ipaddr_sexp.(V4.t_of_sexp (V4.sexp_of_t os)) in
        let ts = V4.to_string os in
        assert_equal ~msg:addr ts rt)
      addrs

  let test_string_rt_bad () =
    let addrs =
      [
        need_more "192.168.0";
        bad_char 11 "192.168.0.1.1";
        error "192.268.2.1" "second octet out of bounds";
        bad_char 4 "192. 168.1.1";
        bad_char 4 "192..0.1";
        bad_char 3 "192,168.0.1";
      ]
    in
    List.iter
      (fun (addr, exn) ->
        assert_raises ~msg:addr exn (fun () -> V4.of_string_exn addr))
      addrs

  let test_string_raw_rt () =
    let addrs =
      [
        (("IP: 192.168.0.1!!!", 4), ("192.168.0.1", 15));
        (("IP: 192.168.0.1.1!!!", 4), ("192.168.0.1", 15));
      ]
    in
    List.iter
      (fun ((addr, off), result) ->
        let c = ref off in
        let os = V4.of_string_raw addr c in
        let ts = V4.to_string os in
        assert_equal ~msg:addr (ts, !c) result)
      addrs

  let test_string_raw_rt_bad () =
    let addrs =
      [
        (let s = "IP: 192.168.0!!!" in
         ((s, 4), (Parse_error ("invalid character '!' at 13", s), 13)));
      ]
    in
    List.iter
      (fun ((addr, off), (exn, cursor)) ->
        let c = ref off in
        assert_raises ~msg:addr exn (fun () -> V4.of_string_raw addr c);
        assert_equal
          ~msg:(Printf.sprintf "%s cursor <> %d (%d)" addr cursor !c)
          !c cursor)
      addrs

  let test_bytes_rt () =
    let addr = "\254\099\003\128" in
    assert_equal ~msg:(String.escaped addr)
      V4.(to_octets (of_octets_exn addr))
      addr

  let test_bytes_rt_bad () =
    let addrs = [ need_more "\254\099\003" ] in
    List.iter
      (fun (addr, exn) ->
        assert_raises ~msg:(String.escaped addr) exn (fun () ->
            V4.of_octets_exn addr))
      addrs

  let test_int32_rt () =
    let addr = 0x0_F0_AB_00_01_l in
    assert_equal
      ~msg:(Printf.sprintf "%08lX" addr)
      V4.(to_int32 (of_int32 addr))
      addr

  let test_prefix_string_rt () =
    let subnets =
      [
        ("192.168.0.0/24", "192.168.0.0/24");
        ("0.0.0.0/0", "0.0.0.0/0");
        ("192.168.0.1/24", "192.168.0.0/24");
        ("192.168.0.0/0", "0.0.0.0/0");
      ]
    in
    List.iter
      (fun (subnet, rt) ->
        let os = V4.Prefix.of_string_exn subnet |> V4.Prefix.prefix in
        let ts = V4.Prefix.to_string os in
        assert_equal ~msg:subnet ts rt;
        let os = Ipaddr_sexp.(V4.Prefix.(t_of_sexp (sexp_of_t os))) in
        let ts = V4.Prefix.to_string os in
        assert_equal ~msg:subnet ts rt)
      subnets

  let test_prefix_string_rt_bad () =
    let subnets =
      [
        bad_char 9 "192.168.0/24";
        bad_char 10 "192.168.0./24";
        error "192.168.0.0/33" "invalid prefix size";
        bad_char 14 "192.168.0.0/30/1";
        bad_char 12 "192.168.0.0/-1";
      ]
    in
    List.iter
      (fun (subnet, exn) ->
        assert_raises ~msg:subnet exn (fun () -> V4.Prefix.of_string_exn subnet))
      subnets

  let test_network_address_rt () =
    let netaddrs = [ ("192.168.0.1/24", "192.168.0.0/24", "192.168.0.1") ] in
    List.iter
      (fun (netaddr, net, addr) ->
        let netv4 = V4.Prefix.of_string_exn net in
        let addrv4 = V4.of_string_exn addr in
        let cidr = V4.Prefix.of_string_exn netaddr in
        let prefix = V4.Prefix.prefix cidr and v4 = V4.Prefix.address cidr in
        assert_equal
          ~msg:(net ^ " <> " ^ V4.Prefix.to_string prefix)
          netv4 prefix;
        assert_equal ~msg:(addr ^ " <> " ^ V4.to_string v4) addrv4 v4;
        let addrstr = V4.Prefix.to_string cidr in
        assert_equal ~msg:(netaddr ^ " <> " ^ addrstr) netaddr addrstr)
      netaddrs

  let test_prefix_broadcast () =
    let pairs =
      [
        ("192.168.0.0/16", "192.168.255.255");
        ("192.168.0.0/24", "192.168.0.255");
        ("192.168.1.1/24", "192.168.1.255");
        ("192.168.0.128/29", "192.168.0.135");
        ("192.168.0.0/31", "192.168.0.1");
        ("192.168.0.0/32", "192.168.0.0");
        ("0.0.0.0/0", "255.255.255.255");
      ]
    in
    List.iter
      (fun (subnet, bcast) ->
        let r = V4.(to_string Prefix.(broadcast (of_string_exn subnet))) in
        assert_equal ~msg:(subnet ^ " <> " ^ r) r bcast)
      pairs

  let test_prefix_bits () =
    let pairs =
      V4.Prefix.
        [
          (global, 0);
          (loopback, 8);
          (link, 16);
          (relative, 8);
          (multicast, 4);
          (private_10, 8);
          (private_172, 12);
          (private_192, 16);
        ]
    in
    List.iter
      (fun (subnet, bits) ->
        let msg = V4.Prefix.to_string subnet ^ " <> " ^ string_of_int bits in
        assert_equal ~msg (V4.Prefix.bits subnet) bits)
      pairs

  let test_prefix_netmask () =
    let nets =
      [
        ("192.168.0.1/32", "255.255.255.255");
        ("192.168.0.1/31", "255.255.255.254");
        ("192.168.0.1/1", "128.0.0.0");
        ("192.168.0.1/0", "0.0.0.0");
      ]
    in
    List.iter
      (fun (net_str, nm_str) ->
        let cidr = V4.Prefix.of_string_exn net_str in
        let prefix = V4.Prefix.prefix cidr
        and address = V4.Prefix.address cidr in
        let netmask = V4.Prefix.netmask prefix in
        let nnm_str = V4.to_string netmask in
        let msg = Printf.sprintf "netmask %s <> %s" nnm_str nm_str in
        assert_equal ~msg nnm_str nm_str;
        let prefix = V4.Prefix.of_netmask_exn ~netmask ~address in
        let nns = V4.Prefix.to_string prefix in
        let msg = Printf.sprintf "%s is %s under netmask iso" net_str nns in
        assert_equal ~msg net_str nns)
      nets

  let test_prefix_netmask_bad () =
    let bad_masks =
      [
        error "127.255.255.255" "invalid netmask";
        error "255.255.254.128" "invalid netmask";
      ]
    in
    List.iter
      (fun (nm_str, exn) ->
        let netmask = V4.of_string_exn nm_str in
        let address = V4.of_string_exn "192.168.0.1" in
        assert_raises ~msg:nm_str exn (fun () ->
            V4.Prefix.of_netmask_exn ~netmask ~address))
      bad_masks

  let test_scope () =
    let ip = V4.of_string_exn in
    (*let is subnet addr = V4.Prefix.(mem addr subnet) in*)
    let is_scope scop addr = scop = V4.scope addr in
    let ships =
      V4.
        [
          (unspecified, "global", is_global, false);
          (unspecified, "multicast", is_multicast, false);
          (unspecified, "point", is_scope Point, true);
          (localhost, "global", is_global, false);
          (localhost, "multicast", is_multicast, false);
          (localhost, "interface", is_scope Interface, true);
          (broadcast, "global", is_global, false);
          (broadcast, "multicast", is_multicast, false);
          (broadcast, "admin", is_scope Admin, true);
          (nodes, "global", is_global, false);
          (nodes, "multicast", is_multicast, true);
          (nodes, "interface", is_scope Link, true);
          (routers, "global", is_global, false);
          (routers, "multicast", is_multicast, true);
          (routers, "link", is_scope Link, true);
          (ip "192.168.0.1", "private", is_private, true);
          (ip "10.3.21.155", "private", is_private, true);
          (ip "172.16.0.0", "private", is_private, true);
          (ip "172.31.255.255", "private", is_private, true);
          (ip "172.15.255.255", "private", is_private, false);
          (ip "172.32.0.0", "private", is_private, false);
        ]
    in
    List.iter
      (fun (addr, lbl, pred, is_mem) ->
        let mems = if is_mem then "" else " not" in
        let msg = V4.to_string addr ^ " is" ^ mems ^ " in " ^ lbl in
        assert_equal ~msg (pred addr) is_mem)
      ships

  let test_map () =
    let m = V4.Map.add (V4.of_string_exn "1.0.0.1") "min" V4.Map.empty in
    let m =
      V4.Map.add (V4.of_string_exn "254.254.254.254") "the greatest host" m
    in
    let m = V4.Map.add (V4.of_string_exn "1.0.0.1") "the least host" m in
    assert_equal ~msg:"size" (V4.Map.cardinal m) 2;
    let min_key, min_val = V4.Map.min_binding m in
    assert_equal
      ~msg:("min is '" ^ min_val ^ "'")
      (min_key, min_val)
      (V4.of_string_exn "1.0.0.1", "the least host");
    assert_equal ~msg:"max" (V4.Map.max_binding m)
      (V4.of_string_exn "254.254.254.254", "the greatest host")

  let test_prefix_map () =
    let module M = Stdlib.Map.Make (V4.Prefix) in
    let of_string s = s |> V4.Prefix.of_string_exn |> V4.Prefix.prefix in
    let m = M.add (of_string "0.0.0.0/0") "everyone" M.empty in
    let m = M.add (of_string "192.0.0.0/1") "weirdos" m in
    let m = M.add (of_string "128.0.0.0/1") "high-bitters" m in
    let m = M.add (of_string "254.0.0.0/8") "top-end" m in
    let m = M.add (of_string "0.0.0.0/0") "iana" m in
    assert_equal ~msg:"size" (M.cardinal m) 3;
    assert_equal ~msg:"min" (M.min_binding m)
      (V4.Prefix.of_string_exn "0.0.0.0/0", "iana");
    assert_equal ~msg:"max" (M.max_binding m)
      (V4.Prefix.of_string_exn "254.0.0.0/8", "top-end");
    assert_equal ~msg:"third"
      (M.find (V4.Prefix.of_string_exn "128.0.0.0/1") m)
      "high-bitters"

  let test_special_addr () =
    assert_equal ~msg:"broadcast" V4.broadcast V4.Prefix.(broadcast global);
    assert_equal ~msg:"any" V4.any V4.Prefix.(network global);
    assert_equal ~msg:"localhost" true V4.(Prefix.(mem localhost loopback))

  let test_multicast_mac () =
    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
    let mac_str = "01:00:5e:3f:9f:8f" in
    assert_equal
      ~msg:("unicast_mac " ^ unicast_mac_str ^ " <> " ^ mac_str)
      unicast_mac_str mac_str;
    assert_equal
      ~msg:("multicast_mac " ^ multicast_mac_str ^ " <> " ^ mac_str)
      multicast_mac_str mac_str

  let test_domain_name () =
    let ip = V4.of_string_exn "128.64.32.16" in
    let name =
      Domain_name.(host_exn (of_string_exn "16.32.64.128.in-addr.arpa"))
    in
    assert_equal ~cmp:Domain_name.equal ~msg:"to_domain_name"
      (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 test_prefix_mem () =
    let ip = V4.of_string_exn in
    let prefix = V4.Prefix.of_string_exn in
    let ships =
      [
        (ip "10.0.0.7", prefix "10.0.0.0/29", true);
        (ip "172.16.255.254", prefix "172.16.255.254/31", true);
        (ip "192.168.0.1", prefix "0.0.0.0/0", true);
        (ip "192.168.0.1", V4.Prefix.private_192, true);
        (ip "255.255.255.255", prefix "255.255.255.255/32", true);
        (ip "192.0.2.1", prefix "192.0.2.0/32", false);
        (ip "192.0.2.1", prefix "192.0.0.0/23", false);
        (ip "255.255.255.255", prefix "0.0.0.0/1", false);
      ]
    in
    List.iter
      (fun (addr, subnet, is_mem) ->
        let msg =
          Printf.sprintf "%s is%s in %s" (V4.to_string addr)
            (if is_mem then "" else " not")
            (V4.Prefix.to_string subnet)
        in
        assert_equal ~msg (V4.Prefix.mem addr subnet) is_mem)
      ships

  let test_succ_pred () =
    let open V4 in
    let printer = function
      | Ok v -> Printf.sprintf "Ok %s" (to_string v)
      | Error (`Msg e) -> Printf.sprintf "Error `Msg \"%s\"" e
    in
    let assert_equal = assert_equal ~printer in
    let ip1 = of_string_exn "0.0.0.0" in
    let ip2 = of_string_exn "255.255.255.255" in
    assert_equal ~msg:"succ 0.0.0.0" (of_string "0.0.0.1") (succ ip1);
    assert_equal ~msg:"succ 255.255.255.255"
      (Error (`Msg "Ipaddr: highest address has been reached"))
      (succ ip2);
    assert_equal ~msg:"succ (succ 255.255.255.255)"
      (Error (`Msg "Ipaddr: highest address has been reached"))
      (succ ip2 >>= succ);
    assert_equal ~msg:"pred 0.0.0.0"
      (Error (`Msg "Ipaddr: lowest address has been reached"))
      (pred ip1);
    ()

  let test_prefix_first_last () =
    let open V4.Prefix in
    let assert_equal = assert_equal ~printer:V4.to_string in
    assert_equal ~msg:"first 192.168.1.0/24"
      (V4.of_string_exn "192.168.1.1")
      (first (of_string_exn "192.168.1.0/24"));
    assert_equal ~msg:"first 169.254.169.254/31"
      (Ipaddr.V4.of_string_exn "169.254.169.254")
      (first (of_string_exn "169.254.169.254/31"));
    assert_equal ~msg:"first 169.254.169.254/32"
      (Ipaddr.V4.of_string_exn "169.254.169.254")
      (first (of_string_exn "169.254.169.254/32"));
    assert_equal ~msg:"last 192.168.1.0/24"
      (Ipaddr.V4.of_string_exn "192.168.1.254")
      (last (of_string_exn "192.168.1.0/24"));
    assert_equal ~msg:"last 169.254.169.254/31"
      (Ipaddr.V4.of_string_exn "169.254.169.255")
      (last (of_string_exn "169.254.169.254/31"));
    assert_equal ~msg:"last 169.254.169.254/32"
      (Ipaddr.V4.of_string_exn "169.254.169.254")
      (last (of_string_exn "169.254.169.254/32"))

  let test_reject_octal () =
    let bad_addrs =
      [
        error "010.8.8.8" "octal notation disallowed";
        error "8.010.8.8" "octal notation disallowed";
        error "8.8.010.8" "octal notation disallowed";
        error "8.8.8.010" "octal notation disallowed";
      ]
    in
    List.iter
      (fun (addr, exn) ->
        assert_raises ~msg:addr exn (fun () -> V4.of_string_exn addr))
      bad_addrs

  let test_reject_prefix_octal () =
    let bad_addrs =
      [
        error "010.8.8.8/32" "octal notation disallowed";
        error "8.010.8.8/32" "octal notation disallowed";
        error "8.8.010.8/32" "octal notation disallowed";
        error "8.8.8.010/32" "octal notation disallowed";
      ]
    in
    List.iter
      (fun (addr, exn) ->
        assert_raises ~msg:addr exn (fun () -> V4.Prefix.of_string_exn addr))
      bad_addrs

  let test_hosts () =
    let nets =
      [
        ("255.255.255.255/32", [ "255.255.255.255" ], false);
        ("255.255.255.255/32", [], true);
        ("255.255.255.254/31", [ "255.255.255.254"; "255.255.255.255" ], true);
        ("255.255.255.254/31", [ "255.255.255.254"; "255.255.255.255" ], false);
        ("255.255.255.252/30", [ "255.255.255.253"; "255.255.255.254" ], true);
        ( "255.255.255.252/30",
          [
            "255.255.255.252";
            "255.255.255.253";
            "255.255.255.254";
            "255.255.255.255";
          ],
          false );
        ( "192.0.2.0/29",
          [
            "192.0.2.0";
            "192.0.2.1";
            "192.0.2.2";
            "192.0.2.3";
            "192.0.2.4";
            "192.0.2.5";
            "192.0.2.6";
            "192.0.2.7";
          ],
          false );
        ( "192.0.2.0/29",
          [
            "192.0.2.1";
            "192.0.2.2";
            "192.0.2.3";
            "192.0.2.4";
            "192.0.2.5";
            "192.0.2.6";
          ],
          true );
      ]
    in
    List.iter
      (fun (net, hosts, usable_flag) ->
        let hosts = List.map V4.of_string_exn hosts in
        let hosts_list =
          List.of_seq
            (V4.Prefix.hosts ~usable:usable_flag (V4.Prefix.of_string_exn net))
        in
        let msg =
          Printf.sprintf
            "incorrect sequence of hosts for %s (usable_flag: %b): %s" net
            usable_flag
            (string_of_list V4.to_string hosts_list)
        in
        assert_equal ~msg hosts_list hosts)
      nets

  let test_subnets () =
    let nets =
      [
        ("255.255.255.255/32", [], 24);
        ("192.0.2.0/24", [], 23);
        ("255.255.255.255/32", [ "255.255.255.255/32" ], 32);
        ( "255.255.255.254/31",
          [ "255.255.255.254/32"; "255.255.255.255/32" ],
          32 );
        ( "255.255.255.252/30",
          [ "255.255.255.252/31"; "255.255.255.254/31" ],
          31 );
        ("192.0.2.0/29", [ "192.0.2.0/30"; "192.0.2.4/30" ], 30);
        ("192.0.2.0/24", [ "192.0.2.0/25"; "192.0.2.128/25" ], 25);
        ("192.0.2.0/24", [ "192.0.2.0/24" ], 24);
        ("10.0.0.0/8", [ "10.0.0.0/9"; "10.128.0.0/9" ], 9);
      ]
    in
    List.iter
      (fun (net, subnets, sz) ->
        let subnets = List.map V4.Prefix.of_string_exn subnets in
        let subnets_list =
          List.of_seq (V4.Prefix.subnets sz (V4.Prefix.of_string_exn net))
        in
        let msg =
          Printf.sprintf
            "incorrect sequence of subnets for %s (prefix length: %i): %s" net
            sz
            (string_of_list V4.Prefix.to_string subnets_list)
        in
        assert_equal ~msg subnets_list subnets)
      nets

  let suite =
    "Test V4"
    >::: [
           "string_rt" >:: test_string_rt;
           "string_rt_bad" >:: test_string_rt_bad;
           "string_raw_rt" >:: test_string_raw_rt;
           "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;
           "network_address_rt" >:: test_network_address_rt;
           "prefix_broadcast" >:: test_prefix_broadcast;
           "prefix_bits" >:: test_prefix_bits;
           "prefix_netmask" >:: test_prefix_netmask;
           "prefix_netmask_bad" >:: test_prefix_netmask_bad;
           "scope" >:: test_scope;
           "map" >:: test_map;
           "prefix_map" >:: test_prefix_map;
           "special_addr" >:: test_special_addr;
           "multicast_mac" >:: test_multicast_mac;
           "domain_name" >:: test_domain_name;
           "prefix_mem" >:: test_prefix_mem;
           "succ_pred" >:: test_succ_pred;
           "prefix_first_last" >:: test_prefix_first_last;
           "reject_octal" >:: test_reject_octal;
           "reject_prefix_octal" >:: test_reject_prefix_octal;
           "hosts" >:: test_hosts;
           "subnets" >:: test_subnets;
         ]
end

module Test_v6 = struct
  let test_string_rt () =
    let addrs =
      [
        ("2001:db8::ff00:42:8329", "2001:db8::ff00:42:8329");
        ("::ffff:192.168.1.1", "::ffff:192.168.1.1");
        ("::", "::");
        ("[::]", "::");
        ("1:1:1:1::1:1:1", "1:1:1:1:0:1:1:1");
        ("0:0:0:1:1:0:0:0", "::1:1:0:0:0");
        ("0:0:0:1:1::", "::1:1:0:0:0");
        ("::1:0:0:0:0", "0:0:0:1::");
        ("FE80::", "fe80::");
        ("::192.168.0.1", "::c0a8:1");
      ]
    in
    List.iter
      (fun (addr, rt) ->
        let os = V6.of_string_exn addr in
        let ts = V6.to_string os in
        assert_equal ~msg:(addr ^ " <> " ^ rt ^ " (" ^ ts ^ ")") ts rt;
        let os = Ipaddr_sexp.(V6.t_of_sexp (V6.sexp_of_t os)) in
        let ts = V6.to_string os in
        assert_equal ~msg:(addr ^ " <> " ^ rt ^ " (" ^ ts ^ ")") ts rt)
      addrs

  let test_string_rt_bad () =
    let addrs =
      [
        need_more "[";
        need_more "[:";
        need_more "[]";
        (* ? *)
        need_more ":";
        need_more "[::";
        bad_char 4 "::1:g:f";
        bad_char 3 "::1::";
        bad_char 4 "1::2::3";
        need_more "1:2:3:4:5:6:7";
        bad_char 15 "1:2:3:4:5:6:7:8:9";
        bad_char 15 "1:2:3:4:5:6:7:8::";
        error "12345::12:2" "component 0 out of bounds";
        bad_char 1 ":1";
      ]
    in
    List.iter
      (fun (addr, exn) ->
        assert_raises ~msg:addr exn (fun () -> V6.of_string_exn addr))
      addrs

  let test_string_raw_rt () =
    let addrs =
      [
        (("IP: 2001:db8::ff00:42:8329!", 4), ("2001:db8::ff00:42:8329", 26));
        (("IP: ::ffff:192.168.1.1 ", 4), ("::ffff:192.168.1.1", 22));
        (("IP: :::", 4), ("::", 6));
        (("IP: [::]:", 4), ("::", 8));
        (("IP: 1:1:1:1::1:1:1:1", 4), ("1:1:1:1:0:1:1:1", 18));
        (("IP: ::1:1:0:0:0::g", 4), ("::1:1:0:0:0", 15));
      ]
    in
    List.iter
      (fun ((addr, off), (result, cursor)) ->
        let c = ref off in
        let os = V6.of_string_raw addr c in
        let ts = V6.to_string os in
        let msg =
          Printf.sprintf "%s at %d: %s at %d <> %s at %d" addr off result cursor
            ts !c
        in
        assert_equal ~msg (ts, !c) (result, cursor))
      addrs

  let test_string_raw_rt_bad () =
    let error (s, c) msg c' = ((s, c), (Parse_error (msg, s), c')) in
    let need_more loc = error loc "not enough data" in
    let bad_char i (s, c) =
      error (s, c) (Printf.sprintf "invalid character '%c' at %d" s.[i] i) i
    in
    let addrs =
      [
        need_more ("IP: [] ", 4) 5;
        bad_char 5 ("IP: : ", 4);
        bad_char 7 ("IP: [:: ", 4);
        bad_char 17 ("IP: 1:2:3:4:5:6:7 ", 4);
        error ("IP: 12345::12:2 ", 4) "component 0 out of bounds" 15;
        bad_char 5 ("IP: :1 ", 4);
        need_more ("IP: ::1:1:0:0:0:", 4) 16;
        bad_char 8 ("IP: ::1:g:f ", 4);
      ]
    in
    List.iter
      (fun ((addr, off), (exn, cursor)) ->
        let c = ref off in
        assert_raises ~msg:addr exn (fun () -> V6.of_string_raw addr c);
        assert_equal
          ~msg:(Printf.sprintf "%s cursor <> %d (%d)" addr cursor !c)
          !c cursor)
      addrs

  let test_bytes_rt () =
    let addr =
      "\000\000\000\000\000\000\000\000\000\000\255\255\192\168\000\001"
    in
    let v6 = V6.of_octets_exn addr in
    assert_equal ~printer:String.escaped ~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";
      ]
    in
    List.iter
      (fun (addr, exn) ->
        assert_raises ~msg:(String.escaped addr) exn (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)
    in
    assert_equal
      ~msg:(Printf.sprintf "%08lx %08lx %08lx %08lx" a b c d)
      V6.(to_int32 (of_int32 addr))
      addr

  let test_int64_rt () =
    let tests =
      [
        (0x2a01_04f9_c011_87adL, 0x0_0_0_0L);
        (0x0000_0000_8000_0000L, 0x0_0_0_0L);
      ]
    in
    List.iter
      (fun ((a, b) as addr) ->
        assert_equal
          ~msg:(Printf.sprintf "%016Lx %016Lx" a b)
          V6.(to_int64 (of_int64 addr))
          addr)
      tests

  let test_prefix_string_rt () =
    let subnets =
      [
        ("2000::/3", "2000::/3");
        ("c012::/2", "c000::/2");
        ("ffff:ffff:ffff::ffff/0", "::/0");
        ("::/0", "::/0");
        ("::/128", "::/128");
        ("::1/128", "::1/128");
        ("::/64", "::/64");
        ("[::]/64", "::/64");
      ]
    in
    List.iter
      (fun (subnet, rt) ->
        let os = V6.Prefix.of_string_exn subnet |> V6.Prefix.prefix in
        let ts = V6.Prefix.to_string os in
        assert_equal ~msg:subnet ts rt;
        let os = Ipaddr_sexp.(V6.Prefix.(t_of_sexp (sexp_of_t os))) in
        let ts = V6.Prefix.to_string os in
        assert_equal ~msg:subnet ts rt)
      subnets

  let test_prefix_string_rt_bad () =
    let subnets =
      [
        need_more "/24";
        need_more "::";
        error "::/130" "invalid prefix size";
        bad_char 5 "::/30/1";
        bad_char 7 "2000::/-1";
        bad_char 5 "1::3:/4";
      ]
    in
    List.iter
      (fun (subnet, exn) ->
        assert_raises ~msg:subnet exn (fun () -> V6.Prefix.of_string_exn subnet))
      subnets

  let test_network_address_rt () =
    let netaddrs = [ ("::1/24", "::/24", "::1") ] in
    List.iter
      (fun (netaddr, net, addr) ->
        let netv4 = V6.Prefix.of_string_exn net in
        let addrv4 = V6.of_string_exn addr in
        let cidr = V6.Prefix.of_string_exn netaddr in
        let prefix = V6.Prefix.prefix cidr and v4 = V6.Prefix.address cidr in
        let prefix = V6.Prefix.prefix prefix in
        assert_equal
          ~msg:(net ^ " <> " ^ V6.Prefix.to_string prefix)
          netv4 prefix;
        assert_equal ~msg:(addr ^ " <> " ^ V6.to_string v4) addrv4 v4;
        let addrstr = V6.Prefix.to_string cidr in
        assert_equal ~msg:(netaddr ^ " <> " ^ addrstr) netaddr addrstr)
      netaddrs

  let test_prefix_bits () =
    let pairs =
      V6.Prefix.
        [
          (global_unicast_001, 3);
          (link, 64);
          (unique_local, 7);
          (multicast, 8);
          (ipv4_mapped, 96);
          (noneui64_interface, 3);
        ]
    in
    List.iter
      (fun (subnet, bits) ->
        let msg =
          V6.Prefix.to_string subnet ^ " <> bits " ^ string_of_int bits
        in
        assert_equal ~msg (V6.Prefix.bits subnet) bits)
      pairs

  let test_prefix_netmask () =
    let nets =
      [
        ("8::1/128", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
        ("8::1/127", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe");
        ("8::1/96", "ffff:ffff:ffff:ffff:ffff:ffff::");
        ("8::1/64", "ffff:ffff:ffff:ffff::");
        ("8::1/32", "ffff:ffff::");
        ("8::1/1", "8000::");
        ("8::1/0", "::");
      ]
    in
    List.iter
      (fun (net_str, nm_str) ->
        let cidr = V6.Prefix.of_string_exn net_str in
        let prefix = V6.Prefix.prefix cidr
        and address = V6.Prefix.address cidr in
        let netmask = V6.Prefix.netmask prefix in
        let nnm_str = V6.to_string netmask in
        let msg = Printf.sprintf "netmask %s <> %s" nnm_str nm_str in
        assert_equal ~msg nnm_str nm_str;
        let prefix = V6.Prefix.of_netmask_exn ~netmask ~address in
        let nns = V6.Prefix.to_string prefix in
        let msg = Printf.sprintf "%s is %s under netmask iso" net_str nns in
        assert_equal ~msg net_str nns)
      nets

  let test_prefix_netmask_bad () =
    let bad_masks =
      [
        error "7fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" "invalid netmask";
        error "ffff:ffff:ffff:ffff:ffff:fffe:8000:0" "invalid netmask";
        error "ffff:ffff:ffff:fffe:8000::" "invalid netmask";
        error "ffff:fffe:8000::" "invalid netmask";
      ]
    in
    List.iter
      (fun (nm_str, exn) ->
        let netmask = V6.of_string_exn nm_str in
        let address = V6.of_string_exn "::" in
        assert_raises ~msg:nm_str exn (fun () ->
            V6.Prefix.of_netmask_exn ~netmask ~address))
      bad_masks

  let test_scope () =
    let localhost_v4 = V6.of_string_exn "::ffff:127.0.0.1" in
    let is subnet addr = V6.Prefix.(mem addr subnet) in
    let is_scope scop addr = scop = V6.scope addr in
    let ships =
      V6.
        [
          (unspecified, "global", is_global, false);
          (unspecified, "multicast", is_multicast, false);
          (unspecified, "point", is_scope Point, true);
          (localhost, "global", is_global, false);
          (localhost, "multicast", is_multicast, false);
          (localhost, "interface", is_scope Interface, true);
          (interface_nodes, "global", is_global, false);
          (interface_nodes, "multicast", is_multicast, true);
          (interface_nodes, "interface", is_scope Interface, true);
          (link_nodes, "global", is_global, false);
          (link_nodes, "multicast", is_multicast, true);
          (link_nodes, "link", is_scope Link, true);
          (link_routers, "global", is_global, false);
          (link_routers, "multicast", is_multicast, true);
          (link_routers, "link", is_scope Link, true);
          (localhost_v4, "global", is_global, false);
          (localhost_v4, "multicast", is_multicast, false);
          (localhost_v4, "ipv4", is Prefix.ipv4_mapped, true);
          (localhost_v4, "noneui64", is Prefix.noneui64_interface, true);
          (localhost_v4, "global_001", is Prefix.global_unicast_001, false);
          (localhost_v4, "interface", is_scope Interface, true);
        ]
    in
    List.iter
      (fun (addr, lbl, pred, is_mem) ->
        let mems = if is_mem then "" else " not" in
        let msg = V6.to_string addr ^ " is" ^ mems ^ " in " ^ lbl in
        assert_equal ~msg (pred addr) is_mem)
      ships

  let test_map () =
    let maxs = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" in
    let m = V6.Map.add (V6.of_string_exn "::0:0") "min" V6.Map.empty in
    let m = V6.Map.add (V6.of_string_exn maxs) "the greatest host" m in
    let m = V6.Map.add (V6.of_string_exn "::") "the least host" m in
    assert_equal ~msg:"size" (V6.Map.cardinal m) 2;
    let min_key, min_val = V6.Map.min_binding m in
    assert_equal
      ~msg:("min is '" ^ min_val ^ "'")
      (min_key, min_val)
      (V6.of_string_exn "::0:0:0", "the least host");
    assert_equal ~msg:"max" (V6.Map.max_binding m)
      (V6.of_string_exn maxs, "the greatest host")

  let test_prefix_map () =
    let module M = Stdlib.Map.Make (V6.Prefix) in
    let of_string s = s |> V6.Prefix.of_string_exn |> V6.Prefix.prefix in
    let m = M.add (of_string "::ffff:0.0.0.0/0") "everyone" M.empty in
    let m = M.add (of_string "::ffff:192.0.0.0/1") "weirdos" m in
    let m = M.add (of_string "::ffff:128.0.0.0/1") "high-bitters" m in
    let m = M.add (of_string "::ffff:254.0.0.0/8") "top-end" m in
    let m = M.add (of_string "::ffff:0.0.0.0/0") "iana" m in
    assert_equal ~msg:"size" (M.cardinal m) 3;
    assert_equal ~msg:"min" (M.min_binding m)
      (of_string "::ffff:0.0.0.0/0", "iana");
    assert_equal ~msg:"max" (M.max_binding m)
      (of_string "::ffff:254.0.0.0/8", "top-end");
    assert_equal ~msg:"third"
      (M.find (of_string "::ffff:128.0.0.0/1") m)
      "high-bitters"

  let test_multicast_mac () =
    let on = 0xFFFF in
    let ip = V6.make on on on on on 0xFFFF 0xFEFE 0xFDFD in
    let unicast = V6.Prefix.(network_address global_unicast_001 ip) in
    let multicast = V6.Prefix.(network_address multicast ip) in
    let unicast_mac_str = Macaddr.to_string (V6.multicast_to_mac unicast) in
    let multicast_mac_str = Macaddr.to_string (V6.multicast_to_mac multicast) in
    let mac_str = "33:33:fe:fe:fd:fd" in
    assert_equal
      ~msg:("unicast_mac " ^ unicast_mac_str ^ " <> " ^ mac_str)
      unicast_mac_str mac_str;
    assert_equal
      ~msg:("multicast_mac " ^ multicast_mac_str ^ " <> " ^ mac_str)
      multicast_mac_str mac_str

  let test_domain_name () =
    let ip = V6.of_string_exn "2a00:1450:4009:800::200e" in
    let name =
      "e.0.0.2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.0.9.0.0.4.0.5.4.1.0.0.a.2.ip6.arpa"
    in
    let name = Domain_name.(host_exn (of_string_exn name)) in
    assert_equal ~cmp:Domain_name.equal ~msg:"to_domain_name"
      (V6.to_domain_name ip) name;
    assert_equal ~msg:"of_domain_name" (V6.of_domain_name name) (Some ip)

  let test_link_address_of_mac () =
    let mac = Macaddr.of_string_exn "34-56-78-9A-BC-DE" in
    let ip_str = V6.(to_string (link_address_of_mac mac)) in
    let expected = "fe80::3656:78ff:fe9a:bcde" in
    assert_equal
      ~msg:("link_address_of_mac " ^ ip_str ^ " <> " ^ expected)
      ip_str expected

  let test_succ_pred () =
    let open V6 in
    let printer = function
      | Ok v -> Printf.sprintf "Ok %s" (V6.to_string v)
      | Error (`Msg e) -> Printf.sprintf "Error `Msg \"%s\"" e
    in
    let assert_equal = assert_equal ~printer in
    let ip1 = of_string_exn "::" in
    let ip2 = of_string_exn "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" in
    let ip3 = of_string_exn "::2" in
    assert_equal ~msg:"succ ::" (of_string "::1") (succ ip1);
    assert_equal ~msg:"succ (succ ::)" (of_string "::2") (succ ip1 >>= succ);
    assert_equal ~msg:"succ ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
      (Error (`Msg "Ipaddr: highest address has been reached"))
      (succ ip2);
    assert_equal ~msg:"pred ::2" (of_string "::1") (pred ip3);
    assert_equal ~msg:"pred ::ffff:ffff" (of_string "::ffff:fffd")
      (of_string "::ffff:ffff" >>= pred >>= pred);
    assert_equal ~msg:"pred ::"
      (Error (`Msg "Ipaddr: lowest address has been reached"))
      (pred ip1);
    assert_equal ~msg:"pred (succ ::2)" (Ok ip3) (succ ip3 >>= pred)

  let test_first_last () =
    let open V6 in
    let open Prefix in
    let ip_of_string = V6.of_string_exn in
    let assert_equal = assert_equal ~printer:V6.to_string in
    assert_equal ~msg:"first ::/64" (ip_of_string "::1")
      (first @@ of_string_exn "::/64");
    assert_equal ~msg:"first ::ff00/120" (ip_of_string "::ff01")
      (first @@ of_string_exn "::ff00/120");
    assert_equal ~msg:"first ::aaa0/127" (ip_of_string "::aaa0")
      (first @@ of_string_exn "::aaa0/127");
    assert_equal ~msg:"first ::aaa0/128" (ip_of_string "::aaa0")
      (first @@ of_string_exn "::aaa0/128");
    assert_equal ~msg:"last ::/64"
      (ip_of_string "::ffff:ffff:ffff:ffff")
      (last @@ of_string_exn "::/64");
    assert_equal ~msg:"last ::/120" (ip_of_string "::ff")
      (last @@ of_string_exn "::/120");
    assert_equal ~msg:"last ::/112" (ip_of_string "::ffff")
      (last @@ of_string_exn "::/112");
    assert_equal ~msg:"last ::bbbb:eeee:0000:0000/64"
      (ip_of_string "::ffff:ffff:ffff:ffff")
      (last @@ of_string_exn "::bbbb:eeee:0000:0000/64");
    assert_equal ~msg:"last ::aaa0/127" (ip_of_string "::aaa1")
      (last @@ of_string_exn "::aaa0/127");
    assert_equal ~msg:"last ::aaa0/128" (ip_of_string "::aaa0")
      (last @@ of_string_exn "::aaa0/128")

  let test_hosts () =
    let nets =
      [
        ("2001:db8:0:ffff::/128", [ "2001:db8:0:ffff::" ], false);
        ("2001:db8:0:ffff::/128", [], true);
        ( "2001:db8:0:ffff::/127",
          [ "2001:db8:0:ffff::"; "2001:db8:0:ffff::1" ],
          false );
        ( "2001:db8:0:ffff::/127",
          [ "2001:db8:0:ffff::"; "2001:db8:0:ffff::1" ],
          true );
        ( "2001:db8:0:ffff::/126",
          [
            "2001:db8:0:ffff::";
            "2001:db8:0:ffff::1";
            "2001:db8:0:ffff::2";
            "2001:db8:0:ffff::3";
          ],
          false );
        ( "2001:db8:0:ffff::/126",
          [ "2001:db8:0:ffff::1"; "2001:db8:0:ffff::2"; "2001:db8:0:ffff::3" ],
          true );
      ]
    in
    List.iter
      (fun (net, hosts, usable_flag) ->
        let hosts = List.map V6.of_string_exn hosts in
        let hosts_list =
          List.of_seq
            (V6.Prefix.hosts ~usable:usable_flag (V6.Prefix.of_string_exn net))
        in
        let msg =
          Printf.sprintf
            "incorrect sequence of hosts for %s (usable_flag: %b): %s" net
            usable_flag
            (string_of_list V6.to_string hosts_list)
        in
        assert_equal ~msg hosts_list hosts)
      nets

  let test_subnets () =
    let nets =
      [
        ("2001:db8:0:ffff::/128", [], 127);
        ("2001:db8:0:ffff::/64", [], 63);
        ("2001:db8:0:ffff::/128", [ "2001:db8:0:ffff::/128" ], 128);
        ( "2001:db8:0:ffff::/127",
          [ "2001:db8:0:ffff::/128"; "2001:db8:0:ffff::1/128" ],
          128 );
        ("::/0", [ "::/1"; "8000::/1" ], 1);
        ("::/0", [ "::/2"; "4000::/2"; "8000::/2"; "c000::/2" ], 2);
        ( "2001:db8:0:ffff::/126",
          [ "2001:db8:0:ffff::/127"; "2001:db8:0:ffff::2/127" ],
          127 );
        ( "2001:db8:0:fff0::/60",
          [
            "2001:db8:0:fff0::/64";
            "2001:db8:0:fff1::/64";
            "2001:db8:0:fff2::/64";
            "2001:db8:0:fff3::/64";
            "2001:db8:0:fff4::/64";
            "2001:db8:0:fff5::/64";
            "2001:db8:0:fff6::/64";
            "2001:db8:0:fff7::/64";
            "2001:db8:0:fff8::/64";
            "2001:db8:0:fff9::/64";
            "2001:db8:0:fffa::/64";
            "2001:db8:0:fffb::/64";
            "2001:db8:0:fffc::/64";
            "2001:db8:0:fffd::/64";
            "2001:db8:0:fffe::/64";
            "2001:db8:0:ffff::/64";
          ],
          64 );
      ]
    in
    List.iter
      (fun (net, subnets, sz) ->
        let subnets = List.map V6.Prefix.of_string_exn subnets in
        let subnets_list =
          List.of_seq (V6.Prefix.subnets sz (V6.Prefix.of_string_exn net))
        in
        let msg =
          Printf.sprintf
            "incorrect sequence of subnets for %s (prefix length: %i): %s" net
            sz
            (string_of_list V6.Prefix.to_string subnets_list)
        in
        assert_equal ~msg subnets_list subnets)
      nets

  let suite =
    "Test V6"
    >::: [
           "string_rt" >:: test_string_rt;
           "string_rt_bad" >:: test_string_rt_bad;
           "string_raw_rt" >:: test_string_raw_rt;
           "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;
           "int64_rt" >:: test_int64_rt;
           "prefix_string_rt" >:: test_prefix_string_rt;
           "prefix_string_rt_bad" >:: test_prefix_string_rt_bad;
           "network_address_rt" >:: test_network_address_rt;
           "prefix_bits" >:: test_prefix_bits;
           "prefix_netmask" >:: test_prefix_netmask;
           "prefix_netmask_bad" >:: test_prefix_netmask_bad;
           "scope" >:: test_scope;
           "map" >:: test_map;
           "prefix_map" >:: test_prefix_map;
           "multicast_mac" >:: test_multicast_mac;
           "domain_name" >:: test_domain_name;
           "link_address_of_mac" >:: test_link_address_of_mac;
           "succ_pred" >:: test_succ_pred;
           "first_last" >:: test_first_last;
           "hosts" >:: test_hosts;
           "subnets" >:: test_subnets;
         ]
end

let test_string_raw_rt () =
  let addrs =
    [
      (("IP: 192.168.0.0!!", 4), ("192.168.0.0", 15));
      (("IP: 192:168:0::!!", 4), ("192:168::", 15));
      (("IP: [192:168::]!!", 4), ("192:168::", 15));
    ]
  in
  List.iter
    (fun ((addr, off), (result, cursor)) ->
      let c = ref off in
      let os = of_string_raw addr c in
      let ts = to_string os in
      let msg =
        Printf.sprintf "%s at %d: %s at %d <> %s at %d" addr off result cursor
          ts !c
      in
      assert_equal ~msg (ts, !c) (result, cursor))
    addrs

let test_with_port_of_string () =
  let default = 8080 in
  let addrs =
    [
      ("127.0.0.1", (Ipaddr.(V4 V4.localhost), default));
      ("127.0.0.1:8080", (Ipaddr.(V4 V4.localhost), 8080));
      ("127.0.0.1:4343", (Ipaddr.(V4 V4.localhost), 4343));
      ("::1", (Ipaddr.(V6 V6.localhost), default));
      ("0:0:0:0:0:0:0:1:8080", (Ipaddr.(V6 V6.localhost), 8080));
      ("0:0:0:0:0:0:0:1:4343", (Ipaddr.(V6 V6.localhost), 4343));
    ]
  in
  List.iter
    (fun (inet_addr, result) ->
      match Ipaddr.with_port_of_string ~default inet_addr with
      | Ok ((V4 ipv4, port) as result') ->
          let result'' = V4.with_port_of_string ~default inet_addr in
          let msg =
            Format.asprintf "%s <> %a:%d" inet_addr Ipaddr.V4.pp ipv4 port
          in
          assert_equal ~msg result result';
          assert_equal ~msg (Ok (ipv4, port)) result''
      | Ok ((V6 ipv6, port) as result') ->
          let result'' = V6.with_port_of_string ~default inet_addr in
          let msg =
            Format.asprintf "%s <> %a:%d" inet_addr Ipaddr.V6.pp ipv6 port
          in
          assert_equal ~msg result result';
          assert_equal ~msg (Ok (ipv6, port)) result''
      | Error (`Msg err) ->
          assert_failure (Format.asprintf "%s: %s" inet_addr err))
    addrs

let test_invalid_with_port_of_string () =
  let default = 8080 in
  let addrs =
    [
      "127.0.0.1:"; "127.0.0.1!8080"; "0:0:0:0:0:0:0:1!8080"; "0:0:0:0:0:0:0:1:";
    ]
  in
  List.iter
    (fun inet_addr ->
      match
        ( Ipaddr.with_port_of_string ~default inet_addr,
          Ipaddr.V4.with_port_of_string ~default inet_addr,
          Ipaddr.V4.with_port_of_string ~default inet_addr )
      with
      | Error _, Error _, Error _ -> ()
      | _ ->
          assert_failure
            (Format.asprintf "Unexpected valid inet_addr: %S" inet_addr))
    addrs

let test_string_raw_rt_bad () =
  let error (s, c) msg c' = ((s, c), (Parse_error (msg, s), c')) in
  let addrs =
    [
      error ("IP: ::192.168 ", 4)
        "not an IPv4 address: invalid character ':' at 4\n\
         not an IPv6 address: invalid character ' ' at 13" 13;
      error ("IP: [::192.168] ", 4)
        "not an IPv4 address: invalid character '[' at 4\n\
         not an IPv6 address: invalid character ']' at 14" 14;
      (* ? *)
      error ("IP: 192:168::3.5 ", 4)
        "not an IPv4 address: invalid character ':' at 7\n\
         not an IPv6 address: invalid character ' ' at 16" 16;
    ]
  in
  List.iter
    (fun ((addr, off), (exn, cursor)) ->
      let c = ref off in
      assert_raises ~msg:addr exn (fun () -> of_string_raw addr c);
      assert_equal
        ~msg:(Printf.sprintf "%s cursor <> %d (%d)" addr cursor !c)
        !c cursor)
    addrs

let test_map () =
  let maxv6 = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" in
  let maxv4 = "254.254.254.254" in
  let m = Map.add (of_string_exn maxv4) "the greatest host v4" Map.empty in
  let m = Map.add (of_string_exn "::0:0") "minv6" m in
  let m = Map.add (of_string_exn maxv6) "the greatest host v6" m in
  let m = Map.add (of_string_exn "::") "the least host v6" m in
  let m = Map.add (of_string_exn "1.0.0.1") "minv4" m in
  let m = Map.add (of_string_exn "1.0.0.1") "the least host v4" m in
  assert_equal ~msg:"size" (Map.cardinal m) 4;
  let min_key, min_val = Map.min_binding m in
  assert_equal
    ~msg:("min is '" ^ min_val ^ "'")
    (min_key, min_val)
    (of_string_exn "1.0.0.1", "the least host v4");
  assert_equal ~msg:"max" (Map.max_binding m)
    (of_string_exn maxv6, "the greatest host v6")

let test_prefix_mem () =
  let ip = of_string_exn in
  let ships =
    [
      (ip "192.168.0.1", V4 V4.Prefix.private_192, true);
      (ip "192.168.0.1", Prefix.of_string_exn "::ffff:0:0/96", true);
      (ip "192.168.0.1", Prefix.of_string_exn "::ffff:0:0/95", true);
      (ip "192.168.0.1", Prefix.of_string_exn "::ffff:0:0/97", false);
      (ip "192.168.0.1", Prefix.of_string_exn "::ffff:128.0.0.0/97", true);
      (ip "::ffff:10.0.0.1", V4 V4.Prefix.private_10, true);
      (ip "::fffe:10.0.0.1", V4 V4.Prefix.private_10, false);
    ]
  in
  List.iter
    (fun (addr, subnet, is_mem) ->
      let msg =
        Printf.sprintf "%s is%s in %s" (to_string addr)
          (if is_mem then "" else " not")
          (Prefix.to_string subnet)
      in
      assert_equal ~msg (Prefix.mem addr subnet) is_mem)
    ships

let test_prefix_subset () =
  let pre = Prefix.of_string_exn in
  let ships =
    [
      (pre "10.0.0.1/32", pre "10.0.0.1/32", true);
      (pre "10.0.0.1/32", pre "10.0.0.2/32", false);
      (pre "10.0.0.3/32", pre "10.0.0.2/31", true);
      (pre "10.0.0.2/31", pre "10.0.0.3/32", false);
      (pre "10.0.10.0/24", V4 V4.Prefix.private_10, true);
      (V4 V4.Prefix.private_10, pre "10.0.10.0/24", false);
    ]
  in
  List.iter
    (fun (subnet1, subnet2, is_subset) ->
      let msg =
        Printf.sprintf "%s is%s subset of %s" (Prefix.to_string subnet1)
          (if is_subset then "" else " not")
          (Prefix.to_string subnet2)
      in
      assert_equal ~msg
        (Prefix.subset ~subnet:subnet1 ~network:subnet2)
        is_subset)
    ships

let suite =
  "Test Generic Addresses"
  >::: [
         "string_raw_rt" >:: test_string_raw_rt;
         "string_raw_rt_bad" >:: test_string_raw_rt_bad;
         "map" >:: test_map;
         "prefix_mem" >:: test_prefix_mem;
         "prefix_subset" >:: test_prefix_subset;
         "with_port" >:: test_with_port_of_string;
         "invalid_with_port" >:: test_invalid_with_port_of_string;
       ]
;;

let _results = run_test_tt_main Test_v4.suite in
let _results = run_test_tt_main Test_v6.suite in
let _results = run_test_tt_main suite in
()
