+;;;--------------------------------------------------------------------------
+;;; tinydns-data output format.
+
+(defun tinydns-output (code &rest fields)
+ (format *zone-output-stream* "~C~{~@[~A~]~^:~}~%" code fields))
+
+(defun tinydns-raw-record (type zr data)
+ (tinydns-output #\: (zr-name zr) type
+ (with-output-to-string (out)
+ (dotimes (i (length data))
+ (let ((byte (aref data i)))
+ (if (or (<= byte 32)
+ (>= byte 128)
+ (member byte '(#\: #\\) :key #'char-code))
+ (format out "\\~3,'0O" byte)
+ (write-char (code-char byte) out)))))
+ (zr-ttl zr)))
+
+(defgeneric tinydns-record (type zr)
+ (:method ((type (eql :a)) zr)
+ (tinydns-output #\+ (zr-name zr)
+ (ipaddr-string (zr-data zr)) (zr-ttl zr)))
+ (:method ((type (eql :aaaa)) zr)
+ (tinydns-output #\3 (zr-name zr)
+ (format nil "~(~32,'0X~)" (ipaddr-addr (zr-data zr)))
+ (zr-ttl zr)))
+ (:method ((type (eql :ptr)) zr)
+ (tinydns-output #\^ (zr-name zr) (zr-data zr) (zr-ttl zr)))
+ (:method ((type (eql :cname)) zr)
+ (tinydns-output #\C (zr-name zr) (zr-data zr) (zr-ttl zr)))
+ (:method ((type (eql :ns)) zr)
+ (tinydns-output #\& (zr-name zr) nil (zr-data zr) (zr-ttl zr)))
+ (:method ((type (eql :mx)) zr)
+ (let ((name (car (zr-data zr)))
+ (prio (cdr (zr-data zr))))
+ (tinydns-output #\@ (zr-name zr) nil name prio (zr-ttl zr))))
+ (:method ((type (eql :txt)) zr)
+ (tinydns-raw-record 16 zr
+ (build-record
+ (dolist (s (zr-data zr))
+ (rec-u8 (length s))
+ (rec-raw-string s)))))
+ (:method ((type (eql :srv)) zr)
+ (destructuring-bind (prio weight port host) (zr-data zr)
+ (tinydns-raw-record 33 zr
+ (build-record
+ (rec-u16 prio)
+ (rec-u16 weight)
+ (rec-u16 port)
+ (rec-name host)))))
+ (:method ((type (eql :sshfp)) zr)
+ (destructuring-bind (alg type fpr) (zr-data zr)
+ (tinydns-raw-record 44 zr
+ (build-record
+ (rec-u8 alg)
+ (rec-u8 type)
+ (do ((i 0 (+ i 2))
+ (n (length fpr)))
+ ((>= i n))
+ (rec-u8 (parse-integer fpr
+ :start i :end (+ i 2)
+ :radix 16))))))))
+
+(defmethod zone-write ((format (eql :tinydns)) zone stream)
+ (format stream "~
+### Zone file `~(~A~)'
+### (generated ~A)
+~%"
+ (zone-name zone)
+ (iso-date :now :datep t :timep t))
+ (let ((soa (zone-soa zone)))
+ (tinydns-output #\Z
+ (zone-name zone)
+ (soa-source soa)
+ (let* ((name (copy-seq (soa-admin soa)))
+ (at (position #\@ name)))
+ (when at (setf (char name at) #\.))
+ name)
+ (soa-serial soa)
+ (soa-refresh soa)
+ (soa-expire soa)
+ (soa-min-ttl soa)
+ (zone-default-ttl zone)))
+ (dolist (zr (zone-records zone))
+ (tinydns-record (zr-type zr) zr)))
+