open Dns (* a useful reference : https://kalfeher.com/https-records-simple/*) let n_of_s = Domain_name.of_string_exn let name_map_ok = let module M = struct type t = Name_rr_map.t let pp = Name_rr_map.pp let equal = Name_rr_map.equal end in (module M: Alcotest.TESTABLE with type t = M.t) let err = let module M = struct type t = [ `Msg of string ] let pp ppf (`Msg s) = Fmt.string ppf s let equal _ _ = true end in (module M: Alcotest.TESTABLE with type t = M.t) module Passing = struct (* From appendix D - test vectors D.1. AliasMode -------------- ; AliasMode example.com. HTTPS 0 foo.example.com. \# 19 ( 00 00 ; priority 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 ; target ) \x00\x00 # priority \x03foo\x07example\x03com\x00 # target *) let alias_mode = {| ; AliasMode example.com. HTTPS 0 foo.example.com. |} let parse_alias_mode () = let rrs = (* let tld = n_of_s "com" in *) let ttl = 3600l in let example = n_of_s "example.com" in let foo_example = n_of_s "foo.example.com" in let https = Https.{ svc_priority = 0 ; target_name = Domain_name.host_exn foo_example ; svc_params = [] ; } in let https' = Rr_map.Https_set.singleton https in Name_rr_map.add example Rr_map.Https (ttl,https') Name_rr_map.empty in Alcotest.(check (result name_map_ok err) "alias mode" (Ok rrs) (Dns_zone.parse alias_mode)) (* D.2. ServiceMode ---------------- ; TargetName Is "." example.com. SVCB 1 . \# 3 ( 00 01 ; priority 00 ; target (root label) ) \x00\x01 # priority \x00 # target (root label) *) let service_mode = {| ; TargetName Is "." example.com. SVCB 1 . |} (* let parse_service_mode () = let rrs = (* let tld = n_of_s "com" in *) let ttl = 3600l in let example = n_of_s "example.com" in let dot = n_of_s "." in let svcb = Svcb.{ svc_priority = 0 ; (* target_name = Domain_name.host_exn dot ; *) target_name = Domain_name. dot ; svc_params = [] ; } in let svcb' = Rr_map.Svcb_set.singleton svcb in Name_rr_map.add example Rr_map.Svcb (ttl,svcb') Name_rr_map.empty in Alcotest.(check (result name_map_ok err) "service mode" (Ok rrs) (Dns_zone.parse alias_mode)) *) (* ; Specifies a port example.com. SVCB 16 foo.example.com. port=53 \# 25 ( 00 10 ; priority 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 ; target 00 03 ; key 3 00 02 ; length 2 00 35 ; value ) \x00\x10 # priority \x03foo\x07example\x03com\x00 # target \x00\x03 # key 3 \x00\x02 # length 2 \x00\x35 # value *) let port_specification = {| ; Specifies a port example.com. SVCB 16 foo.example.com. port=53 |} let parse_port_specification () = let rrs = (* let tld = n_of_s "com" in *) let ttl = 3600l in let example = n_of_s "example.com" in let foo_example = n_of_s "foo.example.com" in let svcb = Svcb.{ svc_priority = 16 ; target_name = Domain_name.host_exn foo_example ; svc_params = [ Port 53 ] ; } in let svcb' = Rr_map.Svcb_set.singleton svcb in Name_rr_map.add example Rr_map.Svcb (ttl,svcb') Name_rr_map.empty in Alcotest.(check (result name_map_ok err) "port specification" (Ok rrs) (Dns_zone.parse port_specification)) (* ; A Generic Key and Unquoted Value example.com. SVCB 1 foo.example.com. key667=hello \# 28 ( 00 01 ; priority 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 ; target 02 9b ; key 667 00 05 ; length 5 68 65 6c 6c 6f ; value ) \x00\x01 # priority \x03foo\x07example\x03com\x00 # target \x02\x9b # key 667 \x00\x05 # length 5 hello # value *) let value_unquoted = {| ; A Generic Key and Unquoted Value example.com. SVCB 1 foo.example.com. key667=hello |} let parse_value_unquoted () = let rrs = (* let tld = n_of_s "com" in *) let ttl = 3600l in let example = n_of_s "example.com" in let foo_example = n_of_s "foo.example.com" in let svcb = Svcb.{ svc_priority = 1 ; target_name = Domain_name.host_exn foo_example ; svc_params = [ Key (667,"hello") ] ; } in let svcb' = Rr_map.Svcb_set.singleton svcb in Name_rr_map.add example Rr_map.Svcb (ttl,svcb') Name_rr_map.empty in Alcotest.(check (result name_map_ok err) "value unquoted" (Ok rrs) (Dns_zone.parse value_unquoted)) (* ; A Generic Key and Quoted Value with a Decimal Escape example.com. SVCB 1 foo.example.com. key667="hello\210qoo" \# 32 ( 00 01 ; priority 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 ; target 02 9b ; key 667 00 09 ; length 9 68 65 6c 6c 6f d2 71 6f 6f ; value ) \x00\x01 # priority \x03foo\x07example\x03com\x00 # target \x02\x9b # key 667 \x00\x09 # length 9 hello\xd2qoo # value *) (* let quoted = {| ; A Generic Key and Quoted Value with a Decimal Escape example.com. SVCB 1 foo.example.com. key667="hello\210qoo" |} let parse_quoted () = let rrs = (* let tld = n_of_s "com" in *) let ttl = 3600l in let example = n_of_s "example.com" in let foo_example = n_of_s "foo.example.com" in let svcb = Svcb.{ svc_priority = 1 ; target_name = Domain_name.host_exn foo_example ; svc_params = [ Key (667,"hello\\210qoo") ] ; } in let svcb' = Rr_map.Svcb_set.singleton svcb in Name_rr_map.add example Rr_map.Svcb (ttl,svcb') Name_rr_map.empty in Alcotest.(check (result name_map_ok err) "value quoted" (Ok rrs) (Dns_zone.parse value_unquoted)) *) (* ; Two Quoted IPv6 Hints example.com. SVCB 1 foo.example.com. ( ipv6hint="2001:db8::1,2001:db8::53:1" ) \# 55 ( 00 01 ; priority 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 ; target 00 06 ; key 6 00 20 ; length 32 20 01 0d b8 00 00 00 00 00 00 00 00 00 00 00 01 ; first address 20 01 0d b8 00 00 00 00 00 00 00 00 00 53 00 01 ; second address ) \x00\x01 # priority \x03foo\x07example\x03com\x00 # target \x00\x06 # key 6 \x00\x20 # length 32 \x20\x01\x0d\xb8\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x01 # first address \x20\x01\x0d\xb8\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x53\x00\x01 # second address *) let quoted_hints = {| ; Two Quoted IPv6 Hints example.com. SVCB 1 foo.example.com. ( ipv6hint="2001:db8::1,2001:db8::53:1" ) |} let parse_quoted_hints () = (* let ip1 = Result.get_ok (Ipaddr.V6.of_string "2001:db8::1") in let ip2 = Result.get_ok (Ipaddr.V6.of_string "2001:db8::53:1") in *) let ip1 = Ipaddr.V6.of_string_exn "2001:db8::1" in let ip2 = Ipaddr.V6.of_string_exn "2001:db8::53:1" in let rrs = (* let tld = n_of_s "com" in *) let ttl = 3600l in let example = n_of_s "example.com" in let foo_example = n_of_s "foo.example.com" in let svcb = Svcb.{ svc_priority = 1 ; target_name = Domain_name.host_exn foo_example ; svc_params = [ Ipv6_hint [ip1;ip2] ] ; } in let svcb' = Rr_map.Svcb_set.singleton svcb in Name_rr_map.add example Rr_map.Svcb (ttl,svcb') Name_rr_map.empty in Alcotest.(check (result name_map_ok err) "quoted hints" (Ok rrs) (Dns_zone.parse quoted_hints)) (* ; An IPv6 Hint Using the Embedded IPv4 Syntax example.com. SVCB 1 example.com. ( ipv6hint="2001:db8:122:344::192.0.2.33" ) \# 35 ( 00 01 ; priority 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 ; target 00 06 ; key 6 00 10 ; length 16 20 01 0d b8 01 22 03 44 00 00 00 00 c0 00 02 21 ; address ) \x00\x01 # priority \x07example\x03com\x00 # target \x00\x06 # key 6 \x00\x10 # length 16 \x20\x01\x0d\xb8\x01\x22\x03\x44 \x00\x00\x00\x00\xc0\x00\x02\x21 # address *) (* let generic_key_and_quoted_hints = {| ; An IPv6 Hint Using the Embedded IPv4 Syntax example.com. SVCB 1 example.com. ( ipv6hint="2001:db8:122:344::192.0.2.33" ) |} let parse_generic_key_and_quoted_hints () = let ip1 = Result.get_ok (Ipaddr.V6.of_string "2001:db8:122:344") in let ip2 = Result.get_ok (Ipaddr.V4.of_string "192.0.2.33") in let rrs = (* let tld = n_of_s "com" in *) let ttl = 3600l in let example = n_of_s "example.com" in let foo_example = n_of_s "foo.example.com" in let svcb = Svcb.{ svc_priority = 1 ; target_name = Domain_name.host_exn foo_example ; svc_params = [ Ipv6_hint [ip1;ip2] ] ; } in let svcb' = Rr_map.Svcb_set.singleton svcb in Name_rr_map.add example Rr_map.Svcb (ttl,svcb') Name_rr_map.empty in Alcotest.(check (result name_map_ok err) "generic key and quoted hints" (Ok rrs) (Dns_zone.parse generic_key_and_quoted_hints)) *) (* ; SvcParamKey Ordering Is Arbitrary in Presentation Format but Sorted in Wire Format example.com. SVCB 16 foo.example.org. ( alpn=h2,h3-19 mandatory=ipv4hint,alpn ipv4hint=192.0.2.1 ) \# 48 ( 00 10 ; priority 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 6f 72 67 00 ; target 00 00 ; key 0 00 04 ; param length 4 00 01 ; value: key 1 00 04 ; value: key 4 00 01 ; key 1 00 09 ; param length 9 02 ; alpn length 2 68 32 ; alpn value 05 ; alpn length 5 68 33 2d 31 39 ; alpn value 00 04 ; key 4 00 04 ; param length 4 c0 00 02 01 ; param value ) \x00\x10 # priority \x03foo\x07example\x03org\x00 # target \x00\x00 # key 0 \x00\x04 # param length 4 \x00\x01 # value: key 1 \x00\x04 # value: key 4 \x00\x01 # key 1 \x00\x09 # param length 9 \x02 # alpn length 2 h2 # alpn value \x05 # alpn length 5 h3-19 # alpn value \x00\x04 # key 4 \x00\x04 # param length 4 \xc0\x00\x02\x01 # param value *) (* ( alpn=h2,h3-19 mandatory=ipv4hint,alpn ipv4hint=192.0.2.1 ) *) let svc_param_key_ordering = {| ; SvcParamKey Ordering Is Arbitrary in Presentation Format but Sorted in Wire Format example.com. SVCB 16 foo.example.org. ( alpn=h2,h3-19 mandatory=ipv4hint,alpn ipv4hint=192.0.2.1 ) |} let parse_svc_param_key_ordering () = let ip = Ipaddr.V4.of_string_exn "192.0.2.1" in let rrs = (* let tld = n_of_s "com" in *) let ttl = 3600l in let example = n_of_s "example.com" in let foo_example = n_of_s "foo.example.org" in let svcb = Svcb.{ svc_priority = 16 ; target_name = Domain_name.host_exn foo_example ; svc_params = [ Mandatory [1;4]; Alpn ["h2"; "h3-19"]; Ipv4_hint [ip] ] ; } in let svcb' = Rr_map.Svcb_set.singleton svcb in Name_rr_map.add example Rr_map.Svcb (ttl,svcb') Name_rr_map.empty in Alcotest.(check (result name_map_ok err) "svc param key ordering" (Ok rrs) (Dns_zone.parse svc_param_key_ordering)) (* Mandatory [1;4]; Alpn ["h2"; "h3-19"]; Ipv4_hint [ip] *) (* ; An "alpn" Value with an Escaped Comma and an Escaped Backslash in Two Presentation Formats example.com. SVCB 16 foo.example.org. alpn="f\\\\oo\\,bar,h2" example.com. SVCB 16 foo.example.org. alpn=f\\\092oo\092,bar,h2 \# 35 ( 00 10 ; priority 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 6f 72 67 00 ; target 00 01 ; key 1 00 0c ; param length 12 08 ; alpn length 8 66 5c 6f 6f 2c 62 61 72 ; alpn value 02 ; alpn length 2 68 32 ; alpn value ) \x00\x10 # priority \x03foo\x07example\x03org\x00 # target \x00\x01 # key 1 \x00\x0c # param length 12 \x08 # alpn length 8 f\oo,bar # alpn value \x02 # alpn length 2 h2 # alpn value *) let escaped_comma_backslash = {| ; An "alpn" Value with an Escaped Comma and an Escaped Backslash in Two Presentation Formats example.com. SVCB 16 foo.example.org. alpn="f\\\\oo\\,bar,h2" example.com. SVCB 16 foo.example.org. alpn=f\\\092oo\092,bar,h2 |} let parse_escaped_comma_backslash () = let rrs = let ttl = 3600l in let example = n_of_s "example.com" in let foo_example = n_of_s "foo.example.org" in let svcb1 = Svcb.{ svc_priority = 16 ; target_name = Domain_name.host_exn foo_example ; svc_params = [ Alpn ["\"f\\\\\\\\oo\\\\"; "bar"; "h2\""] ] ; } in let svcb1' = Rr_map.Svcb_set.singleton svcb1 in let svcb2 = Svcb.{ svc_priority = 16 ; target_name = Domain_name.host_exn foo_example ; svc_params = [ Alpn ["f\\\\\\092oo\\092"; "bar"; "h2"] ] ; } in let svcb2' = Rr_map.Svcb_set.singleton svcb2 in Name_rr_map.(add example Rr_map.Svcb (ttl,svcb2') (singleton example Rr_map.Svcb (ttl,svcb1'))); in Alcotest.(check (result name_map_ok err) "escaped comma backslash" (Ok rrs) (Dns_zone.parse escaped_comma_backslash)) let tests = [ "parse alias mode", `Quick, parse_alias_mode; (* "parse service mode", `Quick, parse_service_mode; *) (* failing on target = '.' *) "parse port specification", `Quick, parse_port_specification; "parse value unquoted", `Quick, parse_value_unquoted; (* "parse quoted", `Quick, parse_quoted; *) "quoted hints", `Quick, parse_quoted_hints; (* "generic key and quoted hints", `Quick, parse_generic_key_and_quoted_hints; *) (* need to implement happy eyeballs v2 synthesis*) "svc param key ordering", `Quick, parse_svc_param_key_ordering; "escaped comma backslash", `Quick, parse_escaped_comma_backslash ] end module Failing = struct (* Failure Cases ------------- ; Multiple Instances of the Same SvcParamKey example.com. SVCB 1 foo.example.com. ( key123=abc key123=def ) *) let failure_svc_param_key = {| ; Multiple Instances of the Same SvcParamKey example.com. SVCB 1 foo.example.com. ( key123=abc key123=def ) |} let parse_failure_svc_param_key () = let _rrs = let ttl = 3600l in let example = n_of_s "example.com" in let foo_example = n_of_s "foo.example.com" in let svcb = Svcb.{ svc_priority = 1 ; target_name = Domain_name.host_exn foo_example ; svc_params = [ Key (123,"abc"); Key (123,"def") ] } in let svcb' = Rr_map.Svcb_set.singleton svcb in Name_rr_map.add example Rr_map.Svcb (ttl,svcb') Name_rr_map.empty in Alcotest.(check (result name_map_ok err) "failure svc param key" (Error (`Msg "SVCB : multiple instances of the same SvcParamKey")) (Dns_zone.parse failure_svc_param_key)) (* ; Missing SvcParamValues That Must Be Non-Empty example.com. SVCB 1 foo.example.com. mandatory example.com. SVCB 1 foo.example.com. alpn example.com. SVCB 1 foo.example.com. port example.com. SVCB 1 foo.example.com. ipv4hint example.com. SVCB 1 foo.example.com. ipv6hint *) (* let failure_missing_svc_param_values = {| ; Missing SvcParamValues That Must Be Non-Empty example.com. SVCB 1 foo.example.com. mandatory example.com. SVCB 1 foo.example.com. alpn example.com. SVCB 1 foo.example.com. port example.com. SVCB 1 foo.example.com. ipv4hint example.com. SVCB 1 foo.example.com. ipv6hint |} Unable to build the key *) (* ; The "no-default-alpn" SvcParamKey Value Must Be Empty example.com. SVCB 1 foo.example.com. no-default-alpn=abc *) (* let failure_no_default_alpn_not_empty = {| ; The "no-default-alpn" SvcParamKey Value Must Be Empty example.com. SVCB 1 foo.example.com. no-default-alpn=abc |} Unable to build the key *) (* ; A Mandatory SvcParam Is Missing example.com. SVCB 1 foo.example.com. mandatory=key123 *) (* let failure_mandatory_svc_param_missing = {| ; A Mandatory SvcParam Is Missing example.com. SVCB 1 foo.example.com. mandatory=key123 |} Unable to build the key *) (* ; The "mandatory" SvcParamKey Must Not Be Included in the Mandatory List example.com. SVCB 1 foo.example.com. mandatory=mandatory *) (* let failure_mandatory_in_mandatory_list = {| ; The "mandatory" SvcParamKey Must Not Be Included in the Mandatory List example.com. SVCB 1 foo.example.com. mandatory=mandatory ) |} Unable to build the key *) (* ; Multiple Instances of the Same SvcParamKey in the Mandatory List example.com. SVCB 1 foo.example.com. ( mandatory=key123,key123 key123=abc ) *) let failure_key_repitition_in_mandatory_list = {| ; Multiple Instances of the Same SvcParamKey in the Mandatory List example.com. SVCB 1 foo.example.com. ( mandatory=key123,key123 key123=abc ) |} let parse_key_repitition_in_mandatory_list () = let _rrs = let ttl = 3600l in let example = n_of_s "example.com" in let foo_example = n_of_s "foo.example.com" in let svcb = Svcb.{ svc_priority = 1 ; target_name = Domain_name.host_exn foo_example ; svc_params = [ Mandatory [123;123]; Key (123,"abc") ] } in let svcb' = Rr_map.Svcb_set.singleton svcb in Name_rr_map.add example Rr_map.Svcb (ttl,svcb') Name_rr_map.empty in Alcotest.(check (result name_map_ok err) "failure key repitition in mandatory list" (Error (`Msg "")) (Dns_zone.parse failure_key_repitition_in_mandatory_list)) let tests = [ "failure_svc_param_key", `Quick, parse_failure_svc_param_key; "key repitition in mandatory list", `Quick, parse_key_repitition_in_mandatory_list ] end let tests = [ "rfc9460 passing", Passing.tests ; "rfc9460 failing", Failing.tests ] let () = Printexc.record_backtrace true; Logs.set_reporter (Logs_fmt.reporter ()); Logs.set_level ~all:true (Some Logs.Debug); Alcotest.run "rfc9460 tests" tests