[racket-dev] [plt] Push #26558: master branch updated

From: Eric Dobson (eric.n.dobson at gmail.com)
Date: Thu Apr 4 13:29:30 EDT 2013

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))))
> +


Posted on the dev mailing list.