#lang racket
(provide new-session close-session session?
new-message new-response message?
message-args message-referer message-id message-flags
read-message write-message
protocol-version
typed typed? untype lump-argument-type? lump-internal-type? lump-external-type?
type:bool type:uint8 type:int16 type:uint16 type:int32 type:uint32
type:int64 type:uint64 type:text type:symbol type:list type:number type:bytes
type:vector)
(require racket/port)
(require (planet williams/packed-binary/packed-binary))
(define (new-session)
(session 0))
(define (close-session session)
(void))
(struct message (id seqnum flags version args referer))
(struct session (counter) #:mutable)
(define new-message
(lambda (id #:referer [referer #f] #:flags [flags 0] #:protocol-version [version protocol-version] . args)
(message id 0 flags version args referer)))
(define (new-response id referer-message #:flags [flags 0] #:protocol-version [version protocol-version] . args)
(message id 0 flags version args (message-seqnum referer-message)))
(struct typed (type value))
(define (untype v)
(if (typed? v)
(typed-value v)
v))
(define protocol-version 1)
(define type:bool 0)
(define type:int8 1)
(define type:uint8 2)
(define type:int16 3)
(define type:uint16 4)
(define type:int32 5)
(define type:uint32 6)
(define type:int64 7)
(define type:uint64 8)
(define type:text 9)
(define type:symbol 10)
(define type:list 11)
(define type:number 12)
(define type:bytes 13)
(define type:vector 14)
(define (lump-internal-type? n)
(and (number? n)
(>= n 0)
(<= n 14)))
(define (lump-external-type? d)
(or (number? d)
(string? d)
(list? d)
(vector? d)
(symbol? d)
(bytes? d)))
(define (lump-argument-type? d)
(or (typed? d)
(lump-external-type? d)))
(define (write-datum/typed type datum out-stream)
(call-with-exception-handler
(lambda (exn)
(raise-type-error 'write-datum "LUMP data type" type))
(lambda ()
((vector-ref
(vector
(lambda ()
(write-packed "<BB" out-stream type:bool (if datum 1 0)))
(lambda ()
(write-packed "<Bb" out-stream type:int8 datum))
(lambda ()
(write-packed "<BB" out-stream type:uint8 datum))
(lambda ()
(write-packed "<Bh" out-stream type:int16 datum))
(lambda ()
(write-packed "<BH" out-stream type:uint16 datum))
(lambda ()
(write-packed "<Bl" out-stream type:int32 datum))
(lambda ()
(write-packed "<BL" out-stream type:uint32 datum))
(lambda ()
(write-packed "<Bq" out-stream type:int64 datum))
(lambda ()
(write-packed "<BQ" out-stream type:uint64 datum))
(lambda ()
(write-packed "<BL" out-stream type:text (string-length datum))
(write-string datum out-stream))
(lambda ()
(let ((s (symbol->string datum)))
(write-packed "<BL" out-stream type:symbol (string-length s))
(write-string s out-stream)))
(lambda ()
(write-packed "<BL" out-stream type:list (length datum))
(for-each
(lambda (element)
(write-datum element out-stream))))
(lambda ()
(let ((s (number->string datum)))
(write-packed "<BL" out-stream type:number (string-length s))
(write-string s out-stream)))
(lambda ()
(write-packed "<BL" out-stream type:bytes (bytes-length datum))
(write-bytes datum out-stream))
(lambda ()
(write-packed "<BL" out-stream type:vector (vector-length datum))
(for ([element datum])
(write-datum element out-stream))))
type)))))
(define (write-datum datum out-stream)
(cond
((typed? datum) (write-datum/typed (typed-type datum) (typed-value datum) out-stream))
((boolean? datum) (write-datum/typed type:bool datum out-stream))
((symbol? datum) (write-datum/typed type:symbol datum out-stream))
((number? datum) (write-datum/typed type:number datum out-stream))
((string? datum) (write-datum/typed type:text datum out-stream))
((bytes? datum) (write-datum/typed type:bytes datum out-stream))
((list? datum) (write-datum/typed type:list datum out-stream))
((vector? datum) (write-datum/typed type:vector datum out-stream))
(else (raise-type-error 'write-datum "LUMP-supported value" datum))))
(define (read-datum in-stream)
(define (read-length in-stream)
(car (read-packed "<L" in-stream)))
(define (read-in pack-type)
(car (read-packed pack-type in-stream)))
(define type (car (read-packed "<B" in-stream)))
(call-with-exception-handler
(lambda (exn)
(raise-type-error 'read-datum "LUMP data type" type))
(lambda ()
((vector-ref
(vector
(lambda ()
(if (> (read-in "<B") 0) #t #f))
(lambda ()
(read-in "<b"))
(lambda ()
(read-in "<B"))
(lambda ()
(read-in "<h"))
(lambda ()
(read-in "<H"))
(lambda ()
(read-in "<l"))
(lambda ()
(read-in "<L"))
(lambda ()
(read-in "<q"))
(lambda ()
(read-in "<Q"))
(lambda ()
(read-string (read-length in-stream) in-stream))
(lambda ()
(string->symbol (read-string (read-length in-stream) in-stream)))
(lambda ()
(for/list ([i (in-range (read-length in-stream))])
(read-datum in-stream)))
(lambda ()
(string->number (read-string (read-length in-stream) in-stream)))
(lambda ()
(read-bytes (read-length in-stream) in-stream))
(lambda ()
(for/vector ([i (in-range (read-length in-stream))])
(read-datum in-stream))))
type)))))
(define (increase-seqnum! s)
(set-session-counter! s (add1 (session-counter s))))
(define (write-message session message [out-stream (current-output-port)])
(define (auto-set-flags flags referer? args?)
(if args?
(bitwise-ior (if referer? (bitwise-ior flags 2) flags) 1)
(if referer? (bitwise-ior flags 2) flags)))
(define (write-args args out)
(write-packed "<H" out (length args))
(for-each
(lambda (datum)
(write-datum datum out))
args))
(increase-seqnum! session)
(write-packed "<BHL" out-stream
(pack-version-and-flags
(message-version message)
(auto-set-flags (message-flags message)
(message-referer message)
(not (null? (message-args message)))))
(message-id message)
(session-counter session))
(when (message-referer message)
(write-packed "<L" out-stream (message-referer message)))
(when (not (null? (message-args message)))
(write-args (message-args message) out-stream))
(session-counter session))
(define (read-message
[in-stream (current-input-port)]
[version-check-proc (lambda (version)
(when (> version protocol-version)
(raise-argument-error 'read-message
(format "LUMP protocol version ~a or lower" protocol-version)
version)))])
(define header (read-packed "<BHL" in-stream))
(define flags (unpack-flags (first header)))
(version-check-proc (unpack-version (first header)))
(define referer (if (bitwise-bit-set? flags 1)
(car (read-packed "<L" in-stream))
#f))
(if (bitwise-bit-set? flags 0)
(message (second header) (third header) flags version (read-args in-stream) referer)
(message (second header) (third header) flags version null referer)))
(define (read-args in-stream)
(for/list ([i (in-range (car (read-packed "<H" in-stream)))])
(read-datum in-stream)))
(define (pack-nibbles b1 b2)
(bitwise-ior (arithmetic-shift b1 4) b2))
(define (unpack-first-nibble n)
(arithmetic-shift n -4))
(define (unpack-second-nibble n)
(bitwise-bit-field n 0 4))
(define (pack-version-and-flags version flags)
(pack-nibbles (sub1 version) flags))
(define (unpack-version b)
(add1 (unpack-first-nibble b)))
(define (unpack-flags b)
(unpack-second-nibble b))