Started CLHS-like docs.
[iolib.git] / net.sockets / address.lisp
blob23a36a134cb6159e80a8e3707888d7c4ded2646a
1 ;;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Indent-tabs-mode: NIL -*-
2 ;;;
3 ;;; address.lisp --- IP address classes.
4 ;;;
5 ;;; Copyright (C) 2006-2007, Stelian Ionescu <sionescu@common-lisp.net>
6 ;;;
7 ;;; This code is free software; you can redistribute it and/or
8 ;;; modify it under the terms of the version 2.1 of
9 ;;; the GNU Lesser General Public License as published by
10 ;;; the Free Software Foundation, as clarified by the
11 ;;; preamble found here:
12 ;;; http://opensource.franz.com/preamble.html
13 ;;;
14 ;;; This program is distributed in the hope that it will be useful,
15 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;;; GNU General Public License for more details.
18 ;;;
19 ;;; You should have received a copy of the GNU Lesser General
20 ;;; Public License along with this library; if not, write to the
21 ;;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
22 ;;; Boston, MA 02110-1301, USA
24 (in-package :net.sockets)
26 ;;;; Class Definitions
28 (defclass address ()
29 ((name :initarg :name :reader address-name :type vector))
30 (:documentation "Base class for all socket address classes."))
32 (defclass inet-address (address) ()
33 (:documentation "Base class for IPv4 and IPv6 addresses."))
35 (defclass ipv4-address (inet-address) ()
36 (:documentation "IPv4 address. Its low-level representation
37 can be accessed as vector of type IPV4-ARRAY through the
38 ADDRESS-NAME reader."))
40 (defclass ipv6-address (inet-address) ()
41 (:documentation "IPv6 address. Its low-level representation
42 can be accessed as vector of type IPV6-ARRAY through the
43 ADDRESS-NAME reader."))
45 (defclass local-address (address)
46 ((abstract :initform nil :initarg :abstract
47 :reader abstract-address-p :type boolean))
48 (:documentation "UNIX socket address."))
50 (defmethod make-load-form ((address inet-address) &optional env)
51 (declare (ignore env))
52 `(make-instance ,(class-of address)
53 :name ,(address-name address)))
55 (defmethod make-load-form ((address local-address) &optional env)
56 (declare (ignore env))
57 `(make-instance ,(class-of address)
58 :name ,(address-name address)
59 :abstrace ,(abstract-address-p address)))
61 ;;;; Conversion functions
63 (defun integer-to-dotted (integer)
64 "Convert a 32-bit unsigned integer to a dotted string."
65 (check-type integer ub32 "an '(unsigned-byte 32)")
66 (format nil "~A.~A.~A.~A"
67 (ldb (byte 8 24) integer)
68 (ldb (byte 8 16) integer)
69 (ldb (byte 8 8) integer)
70 (ldb (byte 8 0) integer)))
72 (defun dotted-to-vector (address)
73 "Convert a dotted IPv4 address to a (simple-array (unsigned-byte 8) 4)."
74 (check-type address string "a string")
75 (let ((addr (make-array 4 :element-type 'ub8 :initial-element 0))
76 (split (split-sequence #\. address :count 5)))
77 (flet ((set-array-value (index str)
78 (setf (aref addr index)
79 (or (parse-number-or-nil str :ub8)
80 (error 'parse-error)))))
81 (let ((len (length split)))
82 (unless (<= 1 len 4)
83 (error 'parse-error))
84 (set-array-value 3 (nth (1- len) split))
85 (loop :for n :in split
86 :for index :below (1- len)
87 :do (set-array-value index n))))
88 (values addr)))
90 (defun dotted-to-integer (address)
91 "Convert a dotted IPv4 address to a 32-bit unsigned integer."
92 (vector-to-integer (dotted-to-vector address)))
94 (defun vector-to-dotted (vector)
95 "Convert an 4-element vector to a dotted string."
96 (coercef vector 'ipv4-array)
97 (let ((*print-pretty* nil))
98 (with-output-to-string (s)
99 (princ (aref vector 0) s) (princ #\. s)
100 (princ (aref vector 1) s) (princ #\. s)
101 (princ (aref vector 2) s) (princ #\. s)
102 (princ (aref vector 3) s))))
104 ;;; TODO: add tests against inet_pton(). Optimize if necessary.
105 ;;; <http://java.sun.com/javase/6/docs/api/java/net/Inet6Address.html#format>
106 (defun colon-separated-to-vector (string)
107 "Convert a colon-separated IPv6 address to a (simple-array ub16 8)."
108 (check-type string string "a string")
109 (when (< (length string) 2)
110 (error 'parse-error))
111 (flet ((handle-trailing-and-leading-colons (string)
112 (let ((start 0)
113 (end (length string))
114 (start-i 0)
115 (trailing-colon-p nil)
116 (tokens-from-leading-or-trailing-zeros 0))
117 (when (char= #\: (char string 0))
118 (incf start)
119 (unless (char= #\: (char string 1))
120 (setq start-i 1)
121 (setq tokens-from-leading-or-trailing-zeros 1)))
122 (when (char= #\: (char string (- end 1)))
123 (setq trailing-colon-p t)
124 (unless (char= #\: (char string (- end 2)))
125 (incf tokens-from-leading-or-trailing-zeros))
126 (decf end))
127 (values start end start-i trailing-colon-p
128 tokens-from-leading-or-trailing-zeros)))
129 (emptyp (string)
130 (= 0 (length string)))
131 ;; we need to use this instead of dotted-to-vector because
132 ;; abbreviated IPv4 addresses are invalid in this context.
133 (ipv4-string-to-ub16-list (string)
134 (let ((tokens (split-sequence #\. string)))
135 (when (= (length tokens) 4)
136 (let ((ipv4 (map 'vector
137 (lambda (string)
138 (let ((x (ignore-errors
139 (parse-integer string))))
140 (if (or (null x) (not (<= 0 x #xff)))
141 (error 'parse-error)
142 x)))
143 tokens)))
144 (list (dpb (aref ipv4 0) (byte 8 8) (aref ipv4 1))
145 (dpb (aref ipv4 2) (byte 8 8) (aref ipv4 3)))))))
146 (parse-hex-ub16 (string)
147 (let ((x (ignore-errors (parse-integer string :radix 16))))
148 (if (or (null x) (not (<= 0 x #xffff)))
149 (error 'parse-error)
150 x))))
151 (multiple-value-bind (start end start-i trailing-colon-p extra-tokens)
152 (handle-trailing-and-leading-colons string)
153 (let* ((vector (make-array 8 :element-type 'ub16 :initial-element 0))
154 (tokens (split-sequence #\: string :start start :end end))
155 (empty-tokens (count-if #'emptyp tokens))
156 (token-count (+ (length tokens) extra-tokens)))
157 (unless trailing-colon-p
158 (let ((ipv4 (ipv4-string-to-ub16-list (lastcar tokens))))
159 (when ipv4
160 (incf token-count)
161 (setq tokens (nconc (butlast tokens) ipv4)))))
162 (when (or (> token-count 8) (> empty-tokens 1)
163 (and (zerop empty-tokens) (/= token-count 8)))
164 (error 'parse-error))
165 (loop for i from start-i and token in tokens do
166 (cond
167 ((integerp token) (setf (aref vector i) token))
168 ((emptyp token) (incf i (- 8 token-count)))
169 (t (setf (aref vector i) (parse-hex-ub16 token)))))
170 vector))))
172 (defun ipv4-on-ipv6-mapped-vector-p (vector)
173 (and (dotimes (i 5 t)
174 (when (plusp (aref vector i))
175 (return nil)))
176 (= (aref vector 5) #xffff)))
178 (defun princ-ipv4-on-ipv6-mapped-address (vector s)
179 (princ "::ffff:" s)
180 (let ((*print-base* 10) (*print-pretty* nil))
181 (princ (ldb (byte 8 8) (aref vector 6)) s) (princ #\. s)
182 (princ (ldb (byte 8 0) (aref vector 6)) s) (princ #\. s)
183 (princ (ldb (byte 8 8) (aref vector 7)) s) (princ #\. s)
184 (princ (ldb (byte 8 0) (aref vector 7)) s)))
186 (defun vector-to-colon-separated (vector &optional (case :downcase))
187 "Convert an 8-element vector to a colon-separated IPv6
188 address. CASE may be :DOWNCASE or :UPCASE."
189 (coercef vector 'ipv6-array)
190 (check-type case (member :upcase :downcase) "either :UPCASE or :DOWNCASE")
191 (let ((s (make-string-output-stream)))
192 (flet ((find-zeros ()
193 (let ((start (position 0 vector :start 1 :end 7)))
194 (when start
195 (values start
196 (position-if #'plusp vector :start start :end 7)))))
197 (princ-subvec (start end)
198 (loop :for i :from start :below end
199 :do (princ (aref vector i) s) (princ #\: s))))
200 (cond
201 ((ipv4-on-ipv6-mapped-vector-p vector)
202 (princ-ipv4-on-ipv6-mapped-address vector s))
204 (let ((*print-base* 16) (*print-pretty* nil))
205 (when (plusp (aref vector 0)) (princ (aref vector 0) s))
206 (princ #\: s)
207 (multiple-value-bind (start end) (find-zeros)
208 (cond (start (princ-subvec 1 start)
209 (princ #\: s)
210 (when end (princ-subvec end 7)))
211 (t (princ-subvec 1 7))))
212 (when (plusp (aref vector 7)) (princ (aref vector 7) s))))))
213 (let ((str (get-output-stream-string s)))
214 (ecase case
215 (:downcase (nstring-downcase str))
216 (:upcase (nstring-upcase str))))))
218 (defmacro ignore-parse-errors (&body body)
219 ;; return first value only
220 `(values (ignore-some-conditions (parse-error) ,@body)))
222 (defun string-address-to-vector (address)
223 "Convert a string address (dotted or colon-separated) to a vector address.
224 If the string is not a valid address, return NIL."
225 (or (ignore-parse-errors (dotted-to-vector address))
226 (ignore-parse-errors (colon-separated-to-vector address))))
228 (defun address-to-vector (address)
229 "Convert any representation of an internet address to a vector.
230 Allowed inputs are: unsigned 32-bit integers, strings, vectors
231 and INET-ADDRESS objects. If the address is valid, two values
232 are returned: the vector and the address type (:IPV4 or IPV6),
233 otherwise NIL is returned."
234 (let (vector addr-type)
235 (typecase address
236 (number (and (ignore-parse-errors
237 (setf vector (integer-to-vector address)))
238 (setf addr-type :ipv4)))
239 (string (cond
240 ((ignore-parse-errors (setf vector (dotted-to-vector address)))
241 (setf addr-type :ipv4))
242 ((ignore-parse-errors
243 (setf vector (colon-separated-to-vector address)))
244 (setf addr-type :ipv6))))
245 ((vector * 4) (and (ignore-parse-errors
246 (setf vector (coerce address 'ipv4-array)))
247 (setf addr-type :ipv4)))
248 ((vector * 8) (and (ignore-parse-errors
249 (setf vector (coerce address 'ipv6-array)))
250 (setf addr-type :ipv6)))
251 (ipv4-address (setf vector (address-name address)
252 addr-type :ipv4))
253 (ipv6-address (setf vector (address-name address)
254 addr-type :ipv6)))
255 (when vector
256 (values vector addr-type))))
258 (defun ensure-address (address &key (family :internet) (errorp t))
259 "If FAMILY is :LOCAL, a LOCAL-ADDRESS is instantiated with
260 ADDRESS as its NAME slot. If FAMILY is :INTERNET, an appropriate
261 subtype of INET-ADDRESS is instantiated after guessing the
262 address type through ADDRESS-TO-VECTOR. If the address is invalid
263 and ERRORP is not NIL, then a CL:PARSE-ERROR is signalled,
264 otherwise NIL is returned.
266 When ADDRESS is already an instance of the ADDRESS class, a check
267 is made to see if it matches the FAMILY argument and it is
268 returned unmodified."
269 (ecase family
270 (:internet
271 (typecase address
272 (address (if errorp
273 (check-type address inet-address "an INET address")
274 (unless (typep address 'inet-address)
275 (return-from ensure-address)))
276 address)
277 (t (let ((vector (address-to-vector address)))
278 (cond
279 (vector (make-address vector))
280 (errorp (error 'parse-error)))))))
281 (:local
282 (etypecase address
283 (string (make-instance 'local-address :name address))
284 (address (if errorp
285 (check-type address local-address "a local address")
286 (unless (typep address 'local-address)
287 (return-from ensure-address)))
288 address)))))
290 ;;;; Print Methods
292 (defgeneric address-to-string (address)
293 (:documentation "Returns a textual presentation of ADDRESS."))
295 (defmethod address-to-string ((address ipv4-address))
296 (vector-to-dotted (address-name address)))
298 (defmethod address-to-string ((address ipv6-address))
299 (vector-to-colon-separated (address-name address)))
301 (defmethod address-to-string ((address local-address))
302 (if (abstract-address-p address)
303 "<unknown socket>"
304 (address-name address)))
306 (defmethod print-object ((address ipv4-address) stream)
307 (format stream "@~A" (address-to-string address)))
309 (defmethod print-object ((address ipv6-address) stream)
310 (format stream "@~A" (address-to-string address)))
312 (defmethod print-object ((address local-address) stream)
313 (print-unreadable-object (address stream :type nil :identity nil)
314 (format stream "Unix socket address: ~A. Abstract: ~:[no~;yes~]"
315 (address-to-string address) (abstract-address-p address))))
317 ;;;; Reader Macro
319 (defun read-literal-ip-address (stream &optional c n)
320 (declare (ignore c n))
321 (loop :with sstr := (make-string-output-stream)
322 :for char := (read-char stream nil nil)
323 :while char
324 :do (cond ((or (digit-char-p char 16)
325 (member char '(#\. #\:) :test #'char=))
326 (write-char char sstr))
328 (unread-char char stream)
329 (loop-finish)))
330 :finally (return (or (ensure-address (get-output-stream-string sstr)
331 :errorp nil)
332 (error 'reader-error :stream stream)))))
334 (set-macro-character #\@ 'read-literal-ip-address t)
336 ;;;; Equality Methods
338 (defun vector-equal (v1 v2)
339 (and (= (length v1) (length v2))
340 (every #'eql v1 v2)))
342 (defgeneric address= (addr1 addr2)
343 (:documentation "Returns T if both arguments are the same socket address."))
345 (defmethod address= ((addr1 inet-address) (addr2 inet-address))
346 (vector-equal (address-name addr1) (address-name addr2)))
348 (defmethod address= ((addr1 local-address) (addr2 local-address))
349 (equal (address-name addr1) (address-name addr2)))
351 (defun address-equal-p (addr1 addr2 &optional (family :internet))
352 "Returns T if both arguments are designators for the same socket address."
353 (address= (ensure-address addr1 :family family)
354 (ensure-address addr2 :family family)))
356 ;;;; Copy Methods
358 (defgeneric copy-address (address)
359 (:documentation
360 "Returns a copy of ADDRESS which is ADDRESS= to the original."))
362 (defmethod copy-address ((addr ipv4-address))
363 (make-instance 'ipv4-address :name (copy-seq (address-name addr))))
365 (defmethod copy-address ((addr ipv6-address))
366 (make-instance 'ipv6-address :name (copy-seq (address-name addr))))
368 (defmethod copy-address ((addr local-address))
369 (make-instance 'local-address
370 :name (copy-seq (address-name addr))
371 :abstract (abstract-address-p addr)))
373 ;;; Returns an IPv6 address by mapping ADDR onto it.
374 (defun map-ipv4-address-to-ipv6 (address)
375 (make-instance 'ipv6-address
376 :name (map-ipv4-vector-to-ipv6 (address-name address))))
378 (defun map-ipv6-address-to-ipv4 (address)
379 (assert (ipv6-ipv4-mapped-p address) (address)
380 "Not an IPv6-mapped IPv4 address: ~A" address)
381 (make-instance 'ipv4-address
382 :name (map-ipv6-vector-to-ipv4 (address-name address))))
384 ;;;; Constructor
386 (defun make-address (name)
387 "Constructs an ADDRESS object. NAME should be of type
388 IPV4-ARRAY, IPV6-ARRAY or STRING in which case an instance of
389 IPV4-ADDRESS, IPV6-ADDRESS or LOCAL-ADDRESS, respectively, will
390 be created. Otherwise, a TYPE-ERROR is signalled. See also
391 ENSURE-ADDRESS."
392 (cond
393 ((ignore-errors (coercef name 'ipv4-array))
394 (make-instance 'ipv4-address :name name))
395 ((ignore-errors (coercef name 'ipv6-array))
396 (make-instance 'ipv6-address :name name))
397 ((stringp name) (make-instance 'local-address :name name))
398 (t (error 'type-error :datum name
399 :expected-type '(or string ipv4-array ipv6-array)))))