Skip to content

Commit 1926be5

Browse files
committed
Use messagepack extensions payloads that can be transmitted
As far as I have been able to understand, cl-messagepack's current implementation of extensions doesn't allow you to encode arbitrary data, associate it with some type, and then communicate this across a network. cl-messagepack's implementation is more like you have some instance of an object, you assign an ID to it, you can then communicate this ID across the network and back, and then lookup this object in a hashtable using the ID. This restricts you to keeping the data in the same lisp image / memory. This is a first stab at what I think is a "true" implementation of messagepack's extensions. It is incomplete, but I'm making the PR for visibility. Use it to encode complex floats with the following: ``` (defun my-encode-complex (data stream) (encode-fixext-8 (append (rest (coerce (flexi-streams:with-output-to-sequence (s) (encode-float (realpart data) s)) 'list)) (rest (coerce (flexi-streams:with-output-to-sequence (s) (encode-float (imagpart data) s)) 'list))) stream 0)) (defun my-decode-complex (len stream) (complex (sb-kernel:make-single-float (ub32->sb32 (load-big-endian stream 4))) (sb-kernel:make-single-float (ub32->sb32 (load-big-endian stream 4)))) ;; (or #+sbcl (sb-kernel:make-single-float (ub32->sb32 (load-big-endian stream 4))) ;; #-(or sbcl) (error "No floating point support yet.")) ;; (or #+sbcl (sb-kernel:make-double-float (ub32->sb32 (load-big-endian stream 4)) ;; (load-big-endian stream 4)) ;; #+ccl (ccl::double-float-from-bits (load-big-endian stream 4) ;; (load-big-endian stream 4)) ;; #-(or sbcl ccl) (error "No floating point support yet.")) ) (register-extension-dispatcher 0 #'complexp #'my-encode-complex CL-USER> (mpk::decode (mpk::encode (list #C(1.2 1.3) #C(0.0 1.0) (list #C(1.2 1.3))))) ```
1 parent d3aa02c commit 1926be5

File tree

1 file changed

+83
-179
lines changed

1 file changed

+83
-179
lines changed

cl-messagepack.lisp

Lines changed: 83 additions & 179 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@
110110
(defvar *decoder-prefers-alists* nil)
111111
(defvar *decode-bin-as-string* nil)
112112

113+
(defvar *extension-dispatchers* (make-hash-table :size #xFF)
114+
"Maps the extension type to a plist of functions whose keys are
115+
:matcher - predicate used by the ENCODE routine to determine who should encode the data (e.g. COMPLEXP)
116+
:encoder - function that encodes the data
117+
:decoder - function that decodes the data")
113118
(defvar *extended-types* nil)
114119
(defvar *lookup-table* nil)
115120

@@ -160,10 +165,9 @@
160165
(encode-hash data stream))
161166
((symbolp data)
162167
(encode-symbol data stream))
163-
((and *extended-types*
164-
(typep data 'extension-type)
165-
(try-encode-ext-type data stream))
166-
t)
168+
((and *extension-dispatchers*
169+
(dispatcher-for-extension-data data))
170+
(funcall (encoder-for-extension-data data) data stream))
167171
(t (error
168172
(format nil
169173
"Cannot encode data ~a (maybe you should bind *extended-types*?)." data)))))
@@ -305,6 +309,68 @@
305309
(or (<= 0 data (1- (expt 2 64)))
306310
(<= (- (expt 2 63)) data (1- (expt 2 63)))))
307311

312+
(defun encode-fixext (data ext type stream)
313+
(assert (<= #xd4 ext #xd8))
314+
(write-byte ext stream)
315+
(write-byte type stream)
316+
(loop :for byte :in data :do (write-byte byte stream)))
317+
318+
(defmacro fixext-encoder (ext size)
319+
`(progn
320+
(defun ,(mksymb 'encode-fixext- size) (data stream type)
321+
(let ((data (alexandria:ensure-list data)))
322+
(assert (<= (length data) ,size))
323+
(encode-fixext data ,ext type stream)))))
324+
325+
(fixext-encoder #xd4 1)
326+
(fixext-encoder #xd5 2)
327+
(fixext-encoder #xd6 4)
328+
(fixext-encoder #xd7 8)
329+
(fixext-encoder #xd8 16)
330+
331+
(defmacro ext-encoder (ext size)
332+
`(progn
333+
(defun ,(mksymb 'encode-ext- size) (data stream type)
334+
(let ((data (alexandria:ensure-list data))
335+
(length (length data)))
336+
(assert (<= length (1- (expt 2 ,size))))
337+
(assert (<= #xc7 ,ext #xc9))
338+
(write-byte ,ext stream)
339+
(store-big-endian (length data) stream ,size)
340+
(write-byte type stream)
341+
(loop :for byte in data :do (write-byte byte stream))))))
342+
343+
(ext-encoder #xc7 8)
344+
(ext-encoder #xc8 16)
345+
(ext-encoder #xc9 32)
346+
347+
(defun register-extension-dispatcher (type matcher encoder decoder)
348+
;; TODO What if those keys don't exist
349+
(setf (gethash type *extension-dispatchers*)
350+
(list :matcher matcher
351+
:encoder encoder
352+
:decoder decoder)))
353+
354+
(defun dispatcher-for-extension-data (data)
355+
"Find the first dispatchers entry whose MATCHER predicate returns T for DATA"
356+
(find data (loop for v being the hash-value of *extension-dispatchers* collect v)
357+
:test (lambda (data hash-val) (funcall (getf hash-val :matcher) data))))
358+
359+
(defun encoder-for-extension-data (data)
360+
(getf (dispatcher-for-extension-data data) :encoder))
361+
362+
(defun decoder-for-extension-data (data)
363+
(getf (dispatcher-for-extension-data data) :decoder))
364+
365+
(defun extension-decode (type len stream)
366+
;; TODO What if decoder doesn't exist?
367+
(funcall (getf (gethash type *extension-dispatchers*) :decoder)
368+
len stream))
369+
370+
(defun extension-encode (type data stream)
371+
(funcall (getf (gethash type *extension-dispatchers*) :encoder)
372+
data stream))
373+
308374
(defun parse-big-endian (byte-array)
309375
;; TODO: do words at once?
310376
(loop with result = 0
@@ -350,22 +416,26 @@
350416
(ub32->sb32 (load-big-endian stream 4)))
351417
((= #xd3 byte)
352418
(ub64->sb64 (load-big-endian stream 8)))
353-
((<= #xd4 byte #xd8) ; fixext1: type, data
419+
((<= #xd4 byte #xd8)
354420
(let ((len (ash 1 (- byte #xd4))))
355-
(typed-data (read-byte stream)
356-
(decode-byte-array len stream))))
421+
(extension-decode (read-byte stream)
422+
len
423+
stream)))
357424
((= #xc7 byte)
358425
(let ((len (read-byte stream)))
359-
(typed-data (read-byte stream)
360-
(decode-byte-array len stream))))
426+
(extension-decode (read-byte stream)
427+
len
428+
stream)))
361429
((= #xc8 byte)
362430
(let ((len (load-big-endian stream 2)))
363-
(typed-data (read-byte stream)
364-
(decode-byte-array len stream))))
431+
(extension-decode (read-byte stream)
432+
len
433+
stream)))
365434
((= #xc9 byte)
366435
(let ((len (load-big-endian stream 4)))
367-
(typed-data (read-byte stream)
368-
(decode-byte-array len stream))))
436+
(extension-decode (read-byte stream)
437+
len
438+
stream)))
369439
((= #xc0 byte)
370440
nil)
371441
((= #xc3 byte)
@@ -473,169 +543,3 @@
473543
(let ((seq (make-array length :element-type '(mod 256))))
474544
(read-sequence seq stream)
475545
(babel:octets-to-string seq)))
476-
477-
478-
479-
;; How to get type-num for the types?
480-
;; A class would have a :allocation :class ...
481-
;; A pointer to the e-t-d would be longer than the int itself.
482-
(defclass extension-type ()
483-
((id :initform (error "need an ID")
484-
:initarg :id
485-
:reader extension-type-id
486-
:writer (setf extension-type-id)
487-
:type (or integer (array (unsigned-byte 8) *))))
488-
(:documentation
489-
"Base type for Ext-Types."))
490-
491-
(defmethod print-object ((obj extension-type) stream)
492-
(print-unreadable-object (obj stream :type T :identity T)
493-
(format stream "~a" (extension-type-id obj))))
494-
495-
496-
(defclass extension-type-description ()
497-
#. (mapcar (lambda (d)
498-
(destructuring-bind (name init &rest rest) d
499-
`(,name :initform ,init
500-
:initarg ,(intern (symbol-name name) :keyword)
501-
:reader ,name
502-
:writer (setf ,name)
503-
,@ rest)))
504-
'((type-number nil :type (integer 0 127))
505-
(encode-with nil :type function)
506-
(decode-with nil :type function)
507-
(as-numeric nil :type (member t nil))
508-
(reg-class nil)
509-
)))
510-
511-
(defmethod print-object ((obj extension-type-description) stream)
512-
(print-unreadable-object (obj stream :type T :identity T)
513-
(format stream "~a ~d"
514-
(class-name (reg-class obj))
515-
(type-number obj))))
516-
517-
518-
(defun symbol-to-extension-type (num sym decode-as)
519-
(assert (member decode-as '(:numeric :byte-array)))
520-
(let ((num? (eq decode-as :numeric)))
521-
(unless (find-class sym nil)
522-
(closer-mop:ensure-class
523-
sym
524-
:direct-superclasses '(extension-type)))
525-
(flet
526-
((maybe-cache (obj id)
527-
(if *lookup-table*
528-
(or
529-
(lookup-table-find num id)
530-
(lookup-table-insert num id obj))
531-
obj)))
532-
(make-instance 'extension-type-description
533-
:type-number num
534-
:reg-class (find-class sym)
535-
:encode-with (lambda (obj)
536-
;; TODO: better use EXTENSION-TYPE-ID?
537-
(let ((id (slot-value obj 'id)))
538-
;; store outgoing objects...
539-
(maybe-cache obj id)
540-
id))
541-
:decode-with (lambda (id)
542-
;; TODO: (if num? ( ... ) x)?
543-
(let ((obj (make-instance sym
544-
'id id)))
545-
;; store incoming objects...
546-
;; TODO: what if that object already exists?
547-
(or
548-
(maybe-cache obj id)
549-
obj)))
550-
:as-numeric num?))))
551-
552-
553-
(defun typed-data (type-num bytes)
554-
(let ((ext-type (find type-num *extended-types*
555-
:test #'eql
556-
:key #'type-number)))
557-
;; TODO: better throw or error?
558-
(assert ext-type)
559-
(funcall (decode-with ext-type)
560-
(if (as-numeric ext-type)
561-
(parse-big-endian bytes)
562-
bytes))))
563-
564-
(defun try-encode-ext-type (obj stream)
565-
(let ((ext-type (find (class-of obj) *extended-types*
566-
:test #'eq
567-
:key #'reg-class)))
568-
;; doesn't run ENCODE-WITH function?!
569-
(when ext-type
570-
(let* ((id (funcall (encode-with ext-type) obj))
571-
(bytes (if (numberp id)
572-
(flexi-streams:with-output-to-sequence (s)
573-
(encode-integer id s))
574-
id))
575-
(len (length bytes)))
576-
;; TODO: in theory the ID might be longer than 256 bytes...
577-
;; (encode-sequence-length bytes stream #xc7 0 #xc8 #xc9)
578-
;; but we need the type inbetween.
579-
(assert (<= 0 len #xff))
580-
(write-byte #xc7 stream)
581-
(write-byte len stream)
582-
(write-byte (type-number ext-type) stream)
583-
(write-sequence bytes stream))
584-
T)))
585-
586-
587-
(defun define-extension-types (args)
588-
"This function defines types for the MessagePack extension type system
589-
(#xD4 to #xD8, and #xC7 to #xC9), and returns a list of them
590-
that can be bound to *EXTENSION-TYPES*.
591-
128 different types can be available simultaneously at any one time.
592-
593-
This function takes integers, flags, and/or closures as arguments;
594-
these get used as items for the next arguments.
595-
* Integers define which type number to use next.
596-
* Flags for decoding:
597-
:BYTE-ARRAY - return the bytes as array. Default.
598-
:NUMERIC - return value in DATA as a number. Only for fixextN.
599-
* A symbol associates the current type number to this type;
600-
this type should be derived from MESSAGEPACK-EXT-TYPE, as
601-
to have a correct MESSAGEPACK:ID slot.
602-
603-
Example:
604-
(defvar *my-extension-types*
605-
(define-extension-types :numeric
606-
5 'buffer 'block
607-
8 'cursor))
608-
Eg., the type 6 would then return (MAKE-BLOCK 'ID <content>)."
609-
(loop with type-num = 0
610-
with decode-as = :byte-array
611-
; with encode
612-
for el in args
613-
append (cond
614-
((numberp el)
615-
(if (<= 0 el 127)
616-
(setf type-num el)
617-
(error "Integer ~a out of range." el))
618-
nil)
619-
((member el '(:byte-array :numeric))
620-
(setf decode-as el)
621-
nil)
622-
((keywordp el)
623-
(error "Keywords ~s not in use." el))
624-
((symbolp el)
625-
(prog1
626-
(list (symbol-to-extension-type type-num el decode-as))
627-
(incf type-num)))
628-
(T
629-
(error "~s not understood." el))
630-
)))
631-
632-
633-
(defun make-lookup-table ()
634-
"Returns something that can be used for *LOOKUP-TABLE*."
635-
(make-hash-table :test #'equalp))
636-
637-
(defun lookup-table-insert (type id obj)
638-
(setf (gethash (cons type id) *lookup-table*) obj))
639-
640-
(defun lookup-table-find (type id)
641-
(gethash (cons type id) *lookup-table*))

0 commit comments

Comments
 (0)