[racket-dev] [plt] Push #26558: master branch updated
Sorry make-immutable-bytes should be bytes->immutable-bytes.
On Thu, Apr 4, 2013 at 10:29 AM, Eric Dobson <eric.n.dobson at gmail.com> wrote:
> Can we make it so that IP addresses are immutable? This would require
> changing make-ip-address to have a call to make-immutable-bytes in
> each case.
>
> On Thu, Apr 4, 2013 at 10:07 AM, <asumu at racket-lang.org> wrote:
>> asumu has updated `master' from 8246d073c0 to 92102a2f07.
>> http://git.racket-lang.org/plt/8246d073c0..92102a2f07
>>
>> =====[ 2 Commits ]======================================================
>> Directory summary:
>> 55.6% collects/net/private/
>> 44.3% collects/net/
>>
>> ~~~~~~~~~~
>>
>> 4e76ae8 Asumu Takikawa <asumu at racket-lang.org> 2013-04-03 15:05
>> :
>> | Add an IP address library
>> |
>> | The library currently lives in a private subfolder so
>> | that the interface can still be changed. The idea is to
>> | eventually make it a top-level `net` library once it is
>> | more mature.
>> :
>> A collects/net/private/ip.rkt
>>
>> ~~~~~~~~~~
>>
>> 92102a2 Asumu Takikawa <asumu at racket-lang.org> 2013-04-04 11:53
>> :
>> | Use net/private/ip in net/dns
>> |
>> | This simplifies the code by outsourcing IP
>> | address functionality to net/private/ip.
>> :
>> M collects/net/dns.rkt | 230 +++++++++++++-----------------------------------
>>
>> =====[ Overall Diff ]===================================================
>>
>> collects/net/dns.rkt
>> ~~~~~~~~~~~~~~~~~~~~
>> --- OLD/collects/net/dns.rkt
>> +++ NEW/collects/net/dns.rkt
>> @@ -2,7 +2,8 @@
>>
>> ;; DNS query library for Racket
>>
>> -(require racket/bool
>> +(require "private/ip.rkt"
>> + racket/bool
>> racket/contract
>> racket/format
>> racket/list
>> @@ -14,13 +15,17 @@
>>
>> (provide (contract-out
>> [dns-get-address
>> - (->* (ip-address-string? string?)
>> + (->* ((or/c ip-address? ip-address-string?) string?)
>> (#:ipv6? any/c)
>> ip-address-string?)]
>> [dns-get-name
>> - (-> ip-address-string? ip-address-string? string?)]
>> + (-> (or/c ip-address? ip-address-string?)
>> + (or/c ip-address? ip-address-string?)
>> + string?)]
>> [dns-get-mail-exchanger
>> - (-> ip-address-string? string? (or/c bytes? string?))]
>> + (-> (or/c ip-address? ip-address-string?)
>> + string?
>> + (or/c bytes? string?))]
>> [dns-find-nameserver
>> (-> (or/c ip-address-string? #f))]))
>>
>> @@ -29,95 +34,8 @@
>> ;; UDP retry timeout:
>> (define INIT-TIMEOUT 50)
>>
>> -;; Contract utilities and Data Definitions
>> -;;
>> +;; Data Definitions
>> ;; An LB is a (Listof Bytes)
>> -;;
>> -;; An IPAddressString passes the following predicate
>> -(define (ip-address-string? val)
>> - (and (string? val)
>> - (or (ipv4-string? val)
>> - (ipv6-string? val))))
>> -
>> -;; String -> Boolean
>> -;; Check if the input string represents an IPv4 address
>> -(define (ipv4-string? str)
>> - ;; String -> Boolean
>> - ;; check if the given string has leading zeroes
>> - (define (has-leading-zeroes? str)
>> - (and (> (string-length str) 1)
>> - (char=? (string-ref str 0) #\0)))
>> - (define matches
>> - (regexp-match #px"^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$"
>> - str))
>> - (and matches
>> - (= (length matches) 5)
>> - ;; check that each octet field is an octet
>> - (andmap byte? (map string->number (cdr matches)))
>> - ;; leading zeroes lead to query errors
>> - (not (ormap has-leading-zeroes? matches))))
>> -
>> -;; String -> Boolean
>> -;; Check if the input string represents an IPv6 address
>> -;; TODO: support dotted quad notation
>> -(define (ipv6-string? str)
>> - (define re-::/: #px"^([0-9a-fA-F]{1,4})(::|:)")
>> - (define re-:: #px"^()(::)")
>> - (define re-: #px"^([0-9a-fA-F]{1,4})(:)")
>> - (define re-end #px"^[0-9a-fA-F]{1,4}$")
>> - (or (regexp-match? #px"^::$" str) ; special case
>> - (let loop ([octet-pairs '()] ; keep octet-pairs to count
>> - [::? #f] ; seen a :: in the string yet?
>> - [str str])
>> - ;; match digit groups and a separator
>> - (define matches
>> - (if ::?
>> - (regexp-match re-: str)
>> - (or (regexp-match re-:: str)
>> - (regexp-match re-::/: str))))
>> - (cond [matches
>> - (match-define (list match digits sep) matches)
>> - (define rest (substring str (string-length match)))
>> - ;; we need to make sure there is only one :: at most
>> - (if (or ::? (string=? sep "::"))
>> - (loop (cons digits octet-pairs) #t rest)
>> - (loop (cons digits octet-pairs) #f rest))]
>> - [else
>> - (and ;; if there isn't a ::, we need 7+1 octet-pairs
>> - (implies (not ::?) (= (length octet-pairs) 7))
>> - ;; this is the +1 octet pair
>> - (regexp-match? re-end str))]))))
>> -
>> -(module+ test
>> - (check-true (ip-address-string? "8.8.8.8"))
>> - (check-true (ip-address-string? "12.81.255.109"))
>> - (check-true (ip-address-string? "192.168.0.1"))
>> - (check-true (ip-address-string? "2001:0db8:85a3:0000:0000:8a2e:0370:7334"))
>> - (check-true (ip-address-string? "2001:200:dff:fff1:216:3eff:feb1:44d7"))
>> - (check-true (ip-address-string? "2001:db8:85a3:0:0:8a2e:370:7334"))
>> - (check-true (ip-address-string? "2001:db8:85a3::8a2e:370:7334"))
>> - (check-true (ip-address-string? "0:0:0:0:0:0:0:1"))
>> - (check-true (ip-address-string? "0:0:0:0:0:0:0:0"))
>> - (check-true (ip-address-string? "::"))
>> - (check-true (ip-address-string? "::0"))
>> - (check-true (ip-address-string? "::ffff:c000:0280"))
>> - (check-true (ip-address-string? "2001:db8::2:1"))
>> - (check-true (ip-address-string? "2001:db8:0:0:1::1"))
>> - (check-false (ip-address-string? ""))
>> - (check-false (ip-address-string? ":::"))
>> - (check-false (ip-address-string? "::0::"))
>> - (check-false (ip-address-string? "2001::db8::2:1"))
>> - (check-false (ip-address-string? "2001:::db8:2:1"))
>> - (check-false (ip-address-string? "52001:db8::2:1"))
>> - (check-false (ip-address-string? "80.8.800.8"))
>> - (check-false (ip-address-string? "80.8.800.0"))
>> - (check-false (ip-address-string? "080.8.800.8"))
>> - (check-false (ip-address-string? "vas8.8.800.8"))
>> - (check-false (ip-address-string? "80.8.128.8dd"))
>> - (check-false (ip-address-string? "0.8.800.008"))
>> - (check-false (ip-address-string? "0.8.800.a8"))
>> - (check-false (ip-address-string? "potatoes"))
>> - (check-false (ip-address-string? "127.0.0")))
>>
>> ;; A Type is one of the following
>> (define types
>> @@ -280,12 +198,14 @@
>> (loop (sub1 n) start (cons rr accum))))))
>>
>> ;; NameServer String Type Class -> (Values Boolean LB LB LB LB LB)
>> -(define (dns-query nameserver addr type class)
>> +(define (dns-query nameserver-ip addr type class)
>> (unless (assoc type types)
>> (raise-type-error 'dns-query "DNS query type" type))
>> (unless (assoc class classes)
>> (raise-type-error 'dns-query "DNS query class" class))
>>
>> + (define nameserver (ip-address->string nameserver-ip))
>> +
>> (let* ([query (make-query (random 256) (string->bytes/latin-1 addr)
>> type class)]
>> [udp (udp-open-socket nameserver 53)]
>> @@ -345,51 +265,22 @@
>> ;; NameServer Address Type Class -> (Values Boolean LB LB LB LB LB)
>> ;; Execute a DNS query and cache it
>> (define (dns-query/cache nameserver addr type class)
>> - (let ([key (string->symbol (format "~a;~a;~a;~a" nameserver addr type class))])
>> - (let ([v (hash-ref cache key (lambda () #f))])
>> - (if v
>> - (apply values v)
>> - (let-values ([(auth? qds ans nss ars reply)
>> - (dns-query nameserver addr type class)])
>> - (hash-set! cache key (list auth? qds ans nss ars reply))
>> - (values auth? qds ans nss ars reply))))))
>> -
>> -(define (ip->string s)
>> - (format "~a.~a.~a.~a"
>> - (list-ref s 0) (list-ref s 1) (list-ref s 2) (list-ref s 3)))
>> + (define key (string->symbol (format "~a;~a;~a;~a" nameserver addr type class)))
>> + (define v (hash-ref cache key (lambda () #f)))
>> + (if v
>> + (apply values v)
>> + (let-values ([(auth? qds ans nss ars reply)
>> + (dns-query nameserver addr type class)])
>> + (hash-set! cache key (list auth? qds ans nss ars reply))
>> + (values auth? qds ans nss ars reply))))
>> +
>> +;; Convert a list of bytes representing an IPv4 address to a string
>> +(define (ip->string lob)
>> + (ip-address->string (ipv4 (list->bytes lob))))
>>
>> ;; Convert a list of bytes representing an IPv6 address to a string
>> (define (ipv6->string lob)
>> - (define two-octets
>> - (for/list ([oct-pair (in-slice 2 (in-list lob))])
>> - (define oct1 (car oct-pair))
>> - (define oct2 (cadr oct-pair))
>> - (+ (arithmetic-shift oct1 8) oct2)))
>> - (define compressed (compress two-octets))
>> - (define compressed-strs
>> - (for/list ([elem compressed])
>> - (if (eq? elem '::)
>> - "" ; string-join will turn this into ::
>> - (~r elem #:base 16))))
>> - (string-join compressed-strs ":"))
>> -
>> -;; (Listof Number) -> (Listof (U Number '::))
>> -;; Compress an IPv6 address to its shortest representation
>> -(define (compress lon)
>> - (let loop ([acc '()] [lon lon])
>> - (cond [(empty? lon) (reverse acc)]
>> - [else
>> - (define zeroes (for/list ([n lon] #:break (not (zero? n))) n))
>> - (define num-zs (length zeroes))
>> - (if (<= num-zs 1)
>> - (loop (cons (car lon) acc) (cdr lon))
>> - (append (reverse acc) '(::) (drop lon num-zs)))])))
>> -
>> -(module+ test
>> - (check-equal? (compress '(0 0 0 5 5)) '(:: 5 5))
>> - (check-equal? (compress '(0 5 5)) '(0 5 5))
>> - (check-equal? (compress '(0 0 5 0 0 5)) '(:: 5 0 0 5))
>> - (check-equal? (compress '(0 5 0 0 0 5)) '(0 5 :: 5)))
>> + (ip-address->string (ipv6 (list->bytes lob))))
>>
>> ;; (NameServer -> (Values Any LB Boolean)) NameServer -> Any
>> ;; Run the given query function, trying until an answer is found
>> @@ -407,48 +298,34 @@
>> (not (member ns tried))
>> (loop ns (cons ns tried)))))))))
>>
>> -;; String -> String
>> +;; IPAddress -> String
>> ;; Convert an IP address to a suitable format for a reverse lookup
>> (define (ip->query-domain ip)
>> - (if (ipv4-string? ip)
>> + (if (ipv4? ip)
>> (ip->in-addr.arpa ip)
>> (ip->ip6.arpa ip)))
>>
>> ;; Convert an IPv4 address for reverse lookup
>> (define (ip->in-addr.arpa ip)
>> - (let ([result (regexp-match #rx"^([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)$"
>> - ip)])
>> - (format "~a.~a.~a.~a.in-addr.arpa"
>> - (list-ref result 4)
>> - (list-ref result 3)
>> - (list-ref result 2)
>> - (list-ref result 1))))
>> + (define bytes (ipv4-bytes ip))
>> + (format "~a.~a.~a.~a.in-addr.arpa"
>> + (bytes-ref bytes 3) (bytes-ref bytes 2)
>> + (bytes-ref bytes 1) (bytes-ref bytes 0)))
>> +
>> +(module+ test
>> + (check-equal? (ip->in-addr.arpa (ipv4 (bytes 8 8 8 8)))
>> + "8.8.8.8.in-addr.arpa")
>> + (check-equal? (ip->in-addr.arpa (ipv4 (bytes 127 0 0 1)))
>> + "1.0.0.127.in-addr.arpa"))
>>
>> ;; Convert an IPv6 address for reverse lookup
>> (define (ip->ip6.arpa ip)
>> - (define has-::? (regexp-match? #rx"::" ip))
>> - (define octet-pair-strings
>> - (cond [has-::?
>> - (define without-:: (regexp-replace #rx"::" ip ":replace-me:"))
>> - (define pieces (regexp-split #rx":" without-::))
>> - (define num-pieces (sub1 (length pieces))) ; don't count replace-me
>> - (flatten
>> - ;; put in as many 0s needed to expand the ::
>> - (for/list ([piece pieces])
>> - (if (string=? piece "replace-me")
>> - (build-list (- 8 num-pieces) (λ _ "0"))
>> - piece)))]
>> - [else (regexp-split #rx":" ip)]))
>> - ;; convert to nibbles
>> (define nibbles
>> (for/fold ([nibbles '()])
>> - ([two-octs octet-pair-strings])
>> - (define n (string->number two-octs 16))
>> - (define nib1 (arithmetic-shift (bitwise-and #xf000 n) -12))
>> - (define nib2 (arithmetic-shift (bitwise-and #x0f00 n) -8))
>> - (define nib3 (arithmetic-shift (bitwise-and #x00f0 n) -4))
>> - (define nib4 (bitwise-and #x000f n))
>> - (append (list nib4 nib3 nib2 nib1) nibbles)))
>> + ([byte (ipv6-bytes ip)])
>> + (define nib1 (arithmetic-shift (bitwise-and #xf0 byte) -4))
>> + (define nib2 (bitwise-and #x0f byte))
>> + (append (list nib2 nib1) nibbles)))
>> (string-append
>> (string-join
>> (for/list ([n nibbles]) (~r n #:base 16))
>> @@ -457,16 +334,23 @@
>>
>> (module+ test
>> (check-equal?
>> - (ip->ip6.arpa "4321:0:1:2:3:4:567:89ab")
>> + (ip->ip6.arpa (make-ip-address "4321:0:1:2:3:4:567:89ab"))
>> "b.a.9.8.7.6.5.0.4.0.0.0.3.0.0.0.2.0.0.0.1.0.0.0.0.0.0.0.1.2.3.4.ip6.arpa")
>> (check-equal?
>> - (ip->ip6.arpa "2001:db8::567:89ab")
>> + (ip->ip6.arpa (make-ip-address "2001:db8::567:89ab"))
>> "b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa"))
>>
>> (define (get-ptr-list-from-ans ans)
>> (filter (lambda (ans-entry) (eq? (list-ref ans-entry 1) 'ptr)) ans))
>>
>> -(define (dns-get-name nameserver ip)
>> +(define (dns-get-name nameserver-ip-or-string ip-or-string)
>> + (define nameserver (if (ip-address? nameserver-ip-or-string)
>> + nameserver-ip-or-string
>> + (make-ip-address nameserver-ip-or-string)))
>> + (define ip (if (ip-address? ip-or-string)
>> + ip-or-string
>> + (make-ip-address ip-or-string)))
>> +
>> (or (try-forwarding
>> (lambda (nameserver)
>> (let-values ([(auth? qds ans nss ars reply)
>> @@ -485,7 +369,10 @@
>> #:when (eq? (list-ref ans-entry 1) type))
>> ans-entry))
>>
>> -(define (dns-get-address nameserver addr #:ipv6? [ipv6? #f])
>> +(define (dns-get-address nameserver-ip-or-string addr #:ipv6? [ipv6? #f])
>> + (define nameserver (if (ip-address? nameserver-ip-or-string)
>> + nameserver-ip-or-string
>> + (make-ip-address nameserver-ip-or-string)))
>> (define type (if ipv6? 'aaaa 'a))
>> (define (get-address nameserver)
>> (define-values (auth? qds ans nss ars reply)
>> @@ -501,7 +388,10 @@
>> (or (try-forwarding get-address nameserver)
>> (error 'dns-get-address "bad address")))
>>
>> -(define (dns-get-mail-exchanger nameserver addr)
>> +(define (dns-get-mail-exchanger nameserver-ip-or-string addr)
>> + (define nameserver (if (ip-address? nameserver-ip-or-string)
>> + nameserver-ip-or-string
>> + (make-ip-address nameserver-ip-or-string)))
>> (or (try-forwarding
>> (lambda (nameserver)
>> (let-values ([(auth? qds ans nss ars reply) (dns-query/cache nameserver addr 'mx 'in)])
>>
>> collects/net/private/ip.rkt
>> ~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> --- /dev/null
>> +++ NEW/collects/net/private/ip.rkt
>> @@ -0,0 +1,323 @@
>> +#lang racket/base
>> +
>> +;; A library for manipulating IP Addresses
>> +
>> +(require racket/bool
>> + racket/contract
>> + racket/format
>> + racket/list
>> + racket/match
>> + racket/string
>> + unstable/sequence)
>> +
>> +(provide
>> + (contract-out
>> + ;; check if a given value is an IP address
>> + [ip-address? (-> any/c boolean?)]
>> +
>> + ;; check if a given string is a valid representation of an IP address
>> + [ip-address-string? (-> string? boolean?)]
>> +
>> + ;; construct an IP address from various inputs
>> + [make-ip-address
>> + (-> (or/c ip-address-string?
>> + (bytes-of-length 4)
>> + (bytes-of-length 16))
>> + ip-address?)]
>> +
>> + ;; construct a string representation of the address
>> + [ip-address->string (-> ip-address? string?)]
>> +
>> + ;; return a byte string representation of the address
>> + [ip-address->bytes (-> ip-address? bytes?)]
>> +
>> + (struct ipv4 ([bytes (bytes-of-length 4)]))
>> + (struct ipv6 ([bytes (bytes-of-length 16)]))))
>> +
>> +(module+ test (require rackunit))
>> +
>> +;; data definitions
>> +
>> +;; An IPAddress is one of
>> +;; (ipv4 4Bytes)
>> +;; (ipv6 16Bytes)
>> +;;
>> +;; interp. an IPv4 address represented as four bytes
>> +;; an IPv6 address represented as sixteen bytes
>> +
>> +(define (ip-address? x) (or (ipv4? x) (ipv6? x)))
>> +
>> +(struct ipv4 (bytes)
>> + #:transparent
>> + #:methods gen:equal+hash
>> + [(define (equal-proc addr1 addr2 rec)
>> + (equal? (ipv4-bytes addr1) (ipv4-bytes addr1)))
>> + (define (hash-proc addr rec) (rec (ipv4-bytes addr)))
>> + (define (hash2-proc addr rec) (rec (ipv4-bytes addr)))])
>> +
>> +(struct ipv6 (bytes)
>> + #:transparent
>> + #:methods gen:equal+hash
>> + [(define (equal-proc addr1 addr2 rec)
>> + (equal? (ipv6-bytes addr1) (ipv6-bytes addr1)))
>> + (define (hash-proc addr rec) (rec (ipv6-bytes addr)))
>> + (define (hash2-proc addr rec) (rec (ipv6-bytes addr)))])
>> +
>> +(define (make-ip-address input)
>> + (match input
>> + ;; TODO: make more efficient by not double checking
>> + [(? ipv4-string?) (ipv4 (ipv4-string->bytes input))]
>> + [(? ipv6-string?) (ipv6 (ipv6-string->bytes input))]
>> + [(? (bytes-of-length 4)) (ipv4 input)]
>> + [(? (bytes-of-length 16)) (ipv6 input)]))
>> +
>> +(module+ test
>> + (check-equal? (make-ip-address "127.0.0.1")
>> + (ipv4 (bytes 127 0 0 1)))
>> + (check-equal? (make-ip-address (bytes 127 0 0 1))
>> + (ipv4 (bytes 127 0 0 1)))
>> + (check-equal? (make-ip-address "2607:f8b0:4009:800::100e")
>> + (ipv6 (bytes 38 7 248 176 64 9 8 0 0 0 0 0 0 0 16 14)))
>> + (check-equal? (make-ip-address (bytes 38 7 248 176 64 9 8 0 0 0 0 0 0 0 16 14))
>> + (ipv6 (bytes 38 7 248 176 64 9 8 0 0 0 0 0 0 0 16 14))))
>> +
>> +(define (ip-address-string? val)
>> + (and (string? val)
>> + (or (ipv4-string? val)
>> + (ipv6-string? val))))
>> +
>> +;; String -> Boolean
>> +;; Check if the input string represents an IPv4 address
>> +(define (ipv4-string? str)
>> + ;; String -> Boolean
>> + ;; check if the given string has leading zeroes
>> + (define (has-leading-zeroes? str)
>> + (and (> (string-length str) 1)
>> + (char=? (string-ref str 0) #\0)))
>> + (define matches
>> + (regexp-match #px"^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$"
>> + str))
>> + (and matches
>> + (= (length matches) 5)
>> + ;; check that each octet field is an octet
>> + (andmap byte? (map string->number (cdr matches)))
>> + ;; leading zeroes lead to query errors
>> + (not (ormap has-leading-zeroes? matches))))
>> +
>> +;; String -> Boolean
>> +;; Check if the input string represents an IPv6 address
>> +;; TODO: support dotted quad notation
>> +(define (ipv6-string? str)
>> + (define re-::/: #px"^([0-9a-fA-F]{1,4})(::|:)")
>> + (define re-:: #px"^()(::)")
>> + (define re-: #px"^([0-9a-fA-F]{1,4})(:)")
>> + (define re-end #px"^[0-9a-fA-F]{1,4}$")
>> + (or (regexp-match? #px"^::$" str) ; special case
>> + (let loop ([octet-pairs '()] ; keep octet-pairs to count
>> + [::? #f] ; seen a :: in the string yet?
>> + [str str])
>> + ;; match digit groups and a separator
>> + (define matches
>> + (if ::?
>> + (regexp-match re-: str)
>> + (or (regexp-match re-:: str)
>> + (regexp-match re-::/: str))))
>> + (cond [matches
>> + (match-define (list match digits sep) matches)
>> + (define rest (substring str (string-length match)))
>> + ;; we need to make sure there is only one :: at most
>> + (if (or ::? (string=? sep "::"))
>> + (loop (cons digits octet-pairs) #t rest)
>> + (loop (cons digits octet-pairs) #f rest))]
>> + [else
>> + (and ;; if there isn't a ::, we need 7+1 octet-pairs
>> + (implies (not ::?) (= (length octet-pairs) 7))
>> + ;; this is the +1 octet pair
>> + (regexp-match? re-end str))]))))
>> +
>> +(module+ test
>> + (check-true (ip-address-string? "8.8.8.8"))
>> + (check-true (ip-address-string? "12.81.255.109"))
>> + (check-true (ip-address-string? "192.168.0.1"))
>> + (check-true (ip-address-string? "2001:0db8:85a3:0000:0000:8a2e:0370:7334"))
>> + (check-true (ip-address-string? "2001:200:dff:fff1:216:3eff:feb1:44d7"))
>> + (check-true (ip-address-string? "2001:db8:85a3:0:0:8a2e:370:7334"))
>> + (check-true (ip-address-string? "2001:db8:85a3::8a2e:370:7334"))
>> + (check-true (ip-address-string? "0:0:0:0:0:0:0:1"))
>> + (check-true (ip-address-string? "0:0:0:0:0:0:0:0"))
>> + (check-true (ip-address-string? "::"))
>> + (check-true (ip-address-string? "::0"))
>> + (check-true (ip-address-string? "::ffff:c000:0280"))
>> + (check-true (ip-address-string? "2001:db8::2:1"))
>> + (check-true (ip-address-string? "2001:db8:0:0:1::1"))
>> + (check-false (ip-address-string? ""))
>> + (check-false (ip-address-string? ":::"))
>> + (check-false (ip-address-string? "::0::"))
>> + (check-false (ip-address-string? "2001::db8::2:1"))
>> + (check-false (ip-address-string? "2001:::db8:2:1"))
>> + (check-false (ip-address-string? "52001:db8::2:1"))
>> + (check-false (ip-address-string? "80.8.800.8"))
>> + (check-false (ip-address-string? "80.8.800.0"))
>> + (check-false (ip-address-string? "080.8.800.8"))
>> + (check-false (ip-address-string? "vas8.8.800.8"))
>> + (check-false (ip-address-string? "80.8.128.8dd"))
>> + (check-false (ip-address-string? "0.8.800.008"))
>> + (check-false (ip-address-string? "0.8.800.a8"))
>> + (check-false (ip-address-string? "potatoes"))
>> + (check-false (ip-address-string? "127.0.0")))
>> +
>> +;; String -> Bytes
>> +;; converts a string representating an IPv4 address to bytes
>> +(define (ipv4-string->bytes ip)
>> + (let ([result (regexp-match #rx"^([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)$"
>> + ip)])
>> + (bytes (string->number (list-ref result 1))
>> + (string->number (list-ref result 2))
>> + (string->number (list-ref result 3))
>> + (string->number (list-ref result 4)))))
>> +
>> +(module+ test
>> + (check-equal? (ipv4-string->bytes "0.8.255.0")
>> + (bytes 0 8 255 0))
>> + (check-equal? (ipv4-string->bytes "8.8.8.8")
>> + (bytes 8 8 8 8))
>> + (check-equal? (ipv4-string->bytes "12.81.255.109")
>> + (bytes 12 81 255 109))
>> + (check-equal? (ipv4-string->bytes "192.168.0.1")
>> + (bytes 192 168 0 1)))
>> +
>> +;; String -> Bytes
>> +;; converts a string representing an IPv6 address to bytes
>> +(define (ipv6-string->bytes ip)
>> + ;; String -> Bytes of length 2
>> + ;; turn a string of two octets and write two bytes
>> + (define (octet-pair-string->bytes two-octs)
>> + (define n (string->number two-octs 16))
>> + (define byte1 (arithmetic-shift (bitwise-and #xff00 n) -8))
>> + (define byte2 (bitwise-and #x00ff n))
>> + (bytes byte1 byte2))
>> +
>> + (define has-::? (regexp-match? #rx"::" ip))
>> + (define splitted (regexp-split #rx":" ip))
>> + (define not-empty-str (filter (λ (s) (not (string=? "" s))) splitted))
>> + (define pad-amount (* 2 (- 8 (length not-empty-str))))
>> + (let loop ([result #""] [splitted splitted])
>> + (cond [(empty? splitted) result]
>> + [(string=? (car splitted) "")
>> + (loop (bytes-append result (make-bytes pad-amount 0))
>> + (remove* '("") (cdr splitted)))]
>> + [else
>> + (loop (bytes-append result (octet-pair-string->bytes (car splitted)))
>> + (cdr splitted))])))
>> +
>> +(module+ test
>> + (check-equal? (ipv6-string->bytes "2001:0db8:85a3:0000:0000:8a2e:0370:7334")
>> + (bytes 32 1 13 184 133 163 0 0 0 0 138 46 3 112 115 52))
>> + (check-equal? (ipv6-string->bytes "2001:200:dff:fff1:216:3eff:feb1:44d7")
>> + (bytes 32 1 2 0 13 255 255 241 2 22 62 255 254 177 68 215))
>> + (check-equal? (ipv6-string->bytes "2001:db8:85a3:0:0:8a2e:370:7334")
>> + (bytes 32 1 13 184 133 163 0 0 0 0 138 46 3 112 115 52))
>> + (check-equal? (ipv6-string->bytes "2001:db8:85a3::8a2e:370:7334")
>> + (bytes 32 1 13 184 133 163 0 0 0 0 138 46 3 112 115 52))
>> + (check-equal? (ipv6-string->bytes "2607:f8b0:4009:800::100e")
>> + (bytes 38 7 248 176 64 9 8 0 0 0 0 0 0 0 16 14))
>> + (check-equal? (ipv6-string->bytes "::1")
>> + (bytes 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1))
>> + (check-equal? (ipv6-string->bytes "::ffff")
>> + (bytes 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255 255)))
>> +
>> +;; IPAddress -> Bytestring
>> +;; Turn an ip struct into a byte string
>> +(define (ip-address->bytes ip)
>> + (match ip
>> + [(? ipv4?) (ipv4-bytes ip)]
>> + [(? ipv6?) (ipv6-bytes ip)]))
>> +
>> +(module+ test
>> + (check-equal? (ip-address->bytes (make-ip-address "8.8.8.8"))
>> + (bytes 8 8 8 8))
>> + (check-equal? (ip-address->bytes (make-ip-address "::1"))
>> + (bytes 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1)))
>> +
>> +;; IPAddress -> String
>> +;; Convert an IP address to a string
>> +(define (ip-address->string ip)
>> + (match ip
>> + [(? ipv4?) (ipv4->string (ipv4-bytes ip))]
>> + [(? ipv6?) (ipv6->string (ipv6-bytes ip))]))
>> +
>> +(module+ test
>> + (check-equal? (ip-address->string (make-ip-address "8.8.8.8"))
>> + "8.8.8.8")
>> + (check-equal? (ip-address->string (make-ip-address "::1"))
>> + "::1"))
>> +
>> +;; Bytes -> String
>> +;; Convert a bytestring for an IPv4 address to a string
>> +(define (ipv4->string bytes)
>> + (string-join (for/list ([b bytes]) (~r b)) "."))
>> +
>> +(module+ test
>> + (check-equal? (ipv4->string (bytes 0 0 0 0)) "0.0.0.0")
>> + (check-equal? (ipv4->string (bytes 255 255 0 1))
>> + "255.255.0.1")
>> + (check-equal? (ipv4->string (bytes 127 0 0 1))
>> + "127.0.0.1")
>> + (check-equal? (ipv4->string (bytes 8 8 8 8))
>> + "8.8.8.8"))
>> +
>> +;; Bytes -> String
>> +;; Convert a bytestring representing an IPv6 address to a string
>> +(define (ipv6->string bytes)
>> + (define two-octets
>> + (for/list ([oct-pair (in-slice 2 (in-bytes bytes))])
>> + (define oct1 (car oct-pair))
>> + (define oct2 (cadr oct-pair))
>> + (+ (arithmetic-shift oct1 8) oct2)))
>> + (define compressed (compress two-octets))
>> + ;; add an extra "" if :: is at the start
>> + (define compressed-strs
>> + (for/list ([elem compressed])
>> + (if (eq? elem '::)
>> + "" ; string-join will turn this into ::
>> + (~r elem #:base 16))))
>> + (define compressed-strs*
>> + (if (string=? (car compressed-strs) "")
>> + (cons "" compressed-strs)
>> + compressed-strs))
>> + (string-join compressed-strs* ":"))
>> +
>> +(module+ test
>> + (check-equal? (ipv6->string (bytes 32 1 13 184 133 163 0 0 0 0 138 46 3 112 115 52))
>> + "2001:db8:85a3::8a2e:370:7334")
>> + (check-equal? (ipv6->string (bytes 38 7 248 176 64 9 8 0 0 0 0 0 0 0 16 14))
>> + "2607:f8b0:4009:800::100e")
>> + (check-equal? (ipv6->string (bytes 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255 255))
>> + "::ffff")
>> + (check-equal? (ipv6->string (bytes 255 255 0 0 0 0 0 0 0 0 0 0 0 0 255 255))
>> + "ffff::ffff"))
>> +
>> +;; (Listof Number) -> (Listof (U Number '::))
>> +;; Compress an IPv6 address to its shortest representation
>> +(define (compress lon)
>> + (let loop ([acc '()] [lon lon])
>> + (cond [(empty? lon) (reverse acc)]
>> + [else
>> + (define zeroes (for/list ([n lon] #:break (not (zero? n))) n))
>> + (define num-zs (length zeroes))
>> + (if (<= num-zs 1)
>> + (loop (cons (car lon) acc) (cdr lon))
>> + (append (reverse acc) '(::) (drop lon num-zs)))])))
>> +
>> +(module+ test
>> + (check-equal? (compress '(0 0 0 5 5)) '(:: 5 5))
>> + (check-equal? (compress '(0 5 5)) '(0 5 5))
>> + (check-equal? (compress '(0 0 5 0 0 5)) '(:: 5 0 0 5))
>> + (check-equal? (compress '(0 5 0 0 0 5)) '(0 5 :: 5)))
>> +
>> +;; contract helper
>> +(define (bytes-of-length n)
>> + (flat-named-contract
>> + `(bytes-of-length ,n)
>> + (λ (bs) (= (bytes-length bs) n))))
>> +