1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
3 ;;; Free Software published under an MIT-like license. See LICENSE ;;;
5 ;;; Copyright (c) 2012-2013 Google, Inc. All rights reserved. ;;;
7 ;;; Original author: Scott McKay ;;;
9 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
11 (in-package "PROTO-IMPL")
14 ;;; Protobuf serialization from Lisp objects
18 (defgeneric make-size-cache (object type)
20 "Make an object size cache for 'object'."))
22 ;; Note that this gets called on the top-level object being serialized
23 ;; This means that either all the objects in the tree should be subclasses
24 ;; of 'base-protobuf-message', or none of them should be. If the root is
25 ;; not a 'base-protobuf-message', then things will work but be slower; if
26 ;; the root is a 'base-protobuf-message', but some children are not, then
27 ;; serialization will fail.
28 (defmethod make-size-cache ((object standard-object) type)
29 (declare (ignore type))
30 ;; No '%cached-size' slot in standard objects, create a "visited" table
33 (defmethod make-size-cache ((object base-protobuf-message) type)
34 ;; In classes defined by Protobufs, we will use the '%cached-size' slot
35 ;; This clears the size cache for the tree of objects
36 (clear-size-cache object type)
40 (declaim (inline cached-object-size))
41 (defun cached-object-size (object visited)
42 (declare #.$optimize-fast-unsafe)
44 (gethash object visited)
45 ;; Warning, Will Robinson! Danger!
46 ;; If there's no 'visited' table, assume there's a cached size slot.
47 ;; We should use methods speciaized on 'base-protobuf-message',
48 ;; but we're trying to max out the speed.
49 (slot-value object '%cached-size)))
51 (defun (setf cached-object-size) (size object visited)
52 (declare #.$optimize-fast-unsafe)
54 (setf (gethash object visited) size)
55 (setf (slot-value object '%cached-size) size)))
58 (defgeneric clear-size-cache (object type)
60 "Clear the size cache for a tree of objects."))
62 (defmethod clear-size-cache ((object standard-object) type)
63 (declare (ignore type))
66 (defmethod clear-size-cache ((object base-protobuf-message) type)
67 (let ((message (find-message-for-class type)))
69 "There is no Protobuf message having the type ~S" type)
70 (macrolet ((read-slot (object slot reader)
72 (funcall ,reader ,object)
73 (slot-value ,object ,slot))))
74 (labels ((do-field (object trace field)
75 (let* ((type (if (eq (proto-class field) 'boolean) :bool (proto-class field)))
76 (slot (proto-value field))
77 (reader (proto-reader field))
79 (when (or slot reader)
80 (cond ((eq (proto-required field) :repeated)
81 (cond ((or (and (proto-packed field) (packed-type-p type))
83 ((typep (setq msg (and type (find-message trace type))) 'protobuf-message)
84 (setf (slot-value object '%cached-size) nil)
85 (doseq (v (if slot (read-slot object slot reader) (list object)))
86 (dolist (f (proto-fields msg))
87 (do-field v msg f))))))
89 (cond ((keywordp type))
90 ((typep (setq msg (and type (find-message trace type))) 'protobuf-message)
91 (setf (slot-value object '%cached-size) nil)
92 (let ((v (if slot (read-slot object slot reader) object)))
94 (dolist (f (proto-fields msg))
95 (do-field v msg f))))))))))))
96 (declare (dynamic-extent #'do-field))
97 (setf (slot-value object '%cached-size) nil)
98 (dolist (field (proto-fields message))
99 (do-field object message field))
105 (defun serialize-object-to-file (filename object type &key visited)
106 "Serializes the object 'object' of type 'type' into the file 'filename'
107 using the wire format.
108 'object' and 'type' are the same as for 'serialize-object-to-bytes'."
109 (with-open-file (stream filename
111 :element-type '(unsigned-byte 8))
112 (serialize-object-to-stream object type :stream stream :visited visited)))
114 (defun serialize-object-to-stream (object type &key (stream *standard-output*) visited)
115 "Serializes the object 'object' of type 'type' onto the stream 'stream'
116 using the wire format.
117 'object' and 'type' are the same as for 'serialize-object-to-bytes'."
118 (let ((buffer (serialize-object-to-bytes object type :visited visited)))
119 (write-sequence buffer stream)
122 (defun serialize-object-to-bytes (object type &key visited)
123 "Serializes the object 'object' of type 'type' into a new byte vector
124 using the wire format.
125 'type' is the Lisp name of a Protobufs message (usually the name of a
126 Lisp class) or a 'protobuf-message'.
127 'visited' is a hash table used to cache object sizes. If it is supplied, it will be
128 cleared before it is used; otherwise, a fresh table will be created if necessary.
129 The return value is the buffer containing the serialized object. If the stream is
130 nil, the buffer is not actually written to anywhere."
131 (let* ((visited (let ((v (or visited (make-size-cache object type))))
134 ;; Use 'object-size' to forcibly recompute all the sizes
135 (size (object-size object type visited))
136 (buffer (make-byte-vector size)))
137 (serialize-object object type buffer 0 visited)
140 ;; Serialize the object using the given protobuf type
143 ;; Allow clients to add their own methods
144 ;; This is how we address the problem of cycles, e.g. -- if you have an object
145 ;; that may contain cycles, serialize the cyclic object using a "handle"
146 (defgeneric serialize-object (object type buffer &optional start visited)
148 "Serializes the object 'object' of type 'type' into the byte array 'buffer'
149 using the wire format.
150 'type' is the Lisp name of a Protobufs message (usually the name of a
151 Lisp class) or a 'protobuf-message'.
152 The object is serialized into the byte array given by 'buffer' starting
153 at the fixnum index 'index' using the wire format.
154 'visited' is a hash table used to cache object sizes; if this is nil, then
155 the object caches its size itself in a '%cached-size' slot.
156 The return value is the buffer containing the serialized object."))
158 (defmethod serialize-object (object type buffer &optional start visited)
159 (let ((message (find-message-for-class type)))
161 "There is no Protobuf message having the type ~S" type)
162 (serialize-object object message buffer start visited)))
164 ;; 'visited' is used to cache object sizes
165 ;; If it's non-nil. it must to be a table with the sizes already in it
166 ;; If it's nil, then the objects must have a '%cached-size' slot
167 ;; The default method uses metadata from the protobuf "schema" for the message
168 (defmethod serialize-object (object (message protobuf-message) buffer &optional start visited)
169 (declare (type (simple-array (unsigned-byte 8)) buffer))
170 (let ((index (or start 0)))
171 (declare (type fixnum index))
172 (macrolet ((read-slot (object slot reader)
173 ;; Don't do a boundp check, we assume the object is fully populated
174 ;; Unpopulated slots should be "nullable" and will contain nil when empty
176 (funcall ,reader ,object)
177 (slot-value ,object ,slot))))
178 (labels ((do-field (object trace field)
179 ;; We don't do cycle detection here
180 ;; If the client needs it, he can define his own 'serialize-object'
181 ;; method to clean things up first
182 (let* ((type (if (eq (proto-class field) 'boolean) :bool (proto-class field)))
183 (slot (proto-value field))
184 (reader (proto-reader field))
186 (when (or slot reader)
187 (cond ((eq (proto-required field) :repeated)
188 (cond ((and (proto-packed field) (packed-type-p type))
189 ;; This is where we handle packed primitive types
190 ;; Packed enums get handled below
191 (let ((tag (make-tag type (proto-index field))))
192 (setq index (serialize-packed (read-slot object slot reader)
193 type tag buffer index))))
195 (let ((tag (make-tag type (proto-index field))))
196 (doseq (v (read-slot object slot reader))
197 (setq index (serialize-prim v type tag buffer index)))))
198 ((typep (setq msg (and type (or (find-message trace type)
199 (find-enum trace type)
200 (find-type-alias trace type))))
202 (if (eq (proto-message-type msg) :group)
203 (doseq (v (if slot (read-slot object slot reader) (list object)))
204 ;; To serialize a group, we encode a start tag,
205 ;; serialize the fields, then encode an end tag
206 (let ((tag1 (make-tag $wire-type-start-group (proto-index field)))
207 (tag2 (make-tag $wire-type-end-group (proto-index field))))
208 (setq index (encode-uint32 tag1 buffer index))
209 (dolist (f (proto-fields msg))
211 (setq index (encode-uint32 tag2 buffer index))))
212 (doseq (v (if slot (read-slot object slot reader) (list object)))
213 ;; To serialize an embedded message, first say that it's
214 ;; a string, then encode its size, then serialize its fields
215 (let ((tag (make-tag $wire-type-string (proto-index field)))
216 (len (cached-object-size v visited)))
217 (setq index (encode-uint32 tag buffer index))
218 (setq index (encode-uint32 len buffer index)))
219 (dolist (f (proto-fields msg))
220 (do-field v msg f)))))
221 ((typep msg 'protobuf-enum)
222 (let ((tag (make-tag $wire-type-varint (proto-index field))))
223 ;; 'proto-packed-p' of enum types returns nil,
224 ;; so packed enum fields won't be handled above
225 (if (proto-packed field)
226 (setq index (serialize-packed-enum (read-slot object slot reader)
227 (proto-values msg) tag buffer index))
228 (doseq (v (read-slot object slot reader))
229 (setq index (serialize-enum v (proto-values msg) tag buffer index))))))
230 ((typep msg 'protobuf-type-alias)
231 (let* ((type (proto-proto-type msg))
232 (tag (make-tag type (proto-index field))))
233 (doseq (v (read-slot object slot reader))
234 (let ((v (funcall (proto-serializer msg) v)))
235 (setq index (serialize-prim v type tag buffer index))))))
237 (undefined-field-type "While serializing ~S,"
238 object type field))))
240 (cond ((eq type :bool)
241 ;; We have to handle optional boolean fields specially
242 ;; because "false" and nil are the same value in Lisp
243 (let ((v (cond ((or (eq (proto-required field) :required)
245 (read-slot object slot reader))
246 ((slot-boundp object slot)
247 (read-slot object slot reader))
249 (unless (eq v :unbound)
250 (let ((tag (make-tag :bool (proto-index field))))
251 (setq index (serialize-prim v type tag buffer index))))))
253 (let ((v (read-slot object slot reader)))
254 (when (and v (not (equal v (proto-default field))))
255 (let ((tag (make-tag type (proto-index field))))
256 (setq index (serialize-prim v type tag buffer index))))))
257 ((typep (setq msg (and type (or (find-message trace type)
258 (find-enum trace type)
259 (find-type-alias trace type))))
261 (let ((v (if slot (read-slot object slot reader) object)))
263 (if (eq (proto-message-type msg) :group)
264 (let ((tag1 (make-tag $wire-type-start-group (proto-index field)))
265 (tag2 (make-tag $wire-type-end-group (proto-index field))))
266 (setq index (encode-uint32 tag1 buffer index))
267 (dolist (f (proto-fields msg))
269 (setq index (encode-uint32 tag2 buffer index)))
270 (let ((tag (make-tag $wire-type-string (proto-index field)))
271 (len (cached-object-size v visited)))
272 (setq index (encode-uint32 tag buffer index))
273 (setq index (encode-uint32 len buffer index))
274 (dolist (f (proto-fields msg))
275 (do-field v msg f)))))))
276 ((typep msg 'protobuf-enum)
277 (let ((v (read-slot object slot reader)))
278 (when (and v (not (eql v (proto-default field))))
279 (let ((tag (make-tag $wire-type-varint (proto-index field))))
280 (setq index (serialize-enum v (proto-values msg) tag buffer index))))))
281 ((typep msg 'protobuf-type-alias)
282 (let ((v (read-slot object slot reader)))
284 (let* ((v (funcall (proto-serializer msg) v))
285 (type (proto-proto-type msg))
286 (tag (make-tag type (proto-index field))))
287 (setq index (serialize-prim v type tag buffer index))))))
289 (undefined-field-type "While serializing ~S,"
290 object type field)))))))))
291 (declare (dynamic-extent #'do-field))
292 (dolist (field (proto-fields message))
293 (do-field object message field))))
294 (values buffer index)))
299 (defun deserialize-object-from-file (type filename)
300 "Deserializes an object of the given type 'type' from the given file
301 as a Protobuf object."
302 (with-open-file (stream filename
304 :element-type '(unsigned-byte 8))
305 (deserialize-object-from-stream type :stream stream)))
307 (defun deserialize-object-from-stream (type &key (stream *standard-input*))
308 "Deserializes an object of the given type 'type' from the given stream
309 as a Protobuf object."
310 (let* ((size (file-length stream))
311 (buffer (make-byte-vector size)))
312 (read-sequence buffer stream)
313 (deserialize-object type buffer 0 size)))
315 (defun deserialize-object-from-bytes (type buffer)
316 "Deserializes an object of the given type 'type' from the given stream
317 as a Protobuf object.
318 'type' is the Lisp name of a Protobufs message (usually the name of a
319 Lisp class) or a 'protobuf-message'.
320 The return value is the object."
321 (deserialize-object type buffer))
323 ;; Allow clients to add their own methods
324 ;; This is you might preserve object identity, e.g.
325 (defgeneric deserialize-object (type buffer &optional start end end-tag)
327 "Deserializes an object of the given type 'type' as a Protobufs object.
328 'type' is the Lisp name of a Protobufs message (usually the name of a
329 Lisp class) or a 'protobuf-message'.
330 The encoded bytes are in the byte array given by 'buffer' starting at
331 the fixnum index 'start' up to the end of the buffer, given by 'end'.
332 'start' defaults to 0, 'end' defaults to the length of the buffer.
333 'end-tag' is used internally to handle the (deprecated) \"group\" feature.
334 The return values are the object and the index at which deserialization stopped.."))
336 (defmethod deserialize-object (type buffer &optional start end (end-tag 0))
337 (let ((message (find-message-for-class type)))
339 "There is no Protobuf message having the type ~S" type)
340 (deserialize-object message buffer start end end-tag)))
342 ;; The default method uses metadata from the protobuf "schema" for the message
343 (defmethod deserialize-object ((message protobuf-message) buffer &optional start end (end-tag 0))
344 (declare (type (simple-array (unsigned-byte 8)) buffer))
345 (let ((index (or start 0))
346 (length (or end (length buffer))))
347 (declare (type fixnum index length))
348 (macrolet ((read-slot (object slot reader)
350 (funcall ,reader ,object)
351 (slot-value ,object ,slot)))
352 (write-slot (object slot writer value)
354 `(let ((,vval ,value))
356 (funcall ,writer ,object ,vval)
357 (setf (slot-value ,object ,slot) ,vval)))))
358 (push-slot (object slot reader writer value)
359 (with-gensyms (vvals)
360 `(let ((,vvals (read-slot ,object ,slot ,reader)))
361 (if (i= (length ,vvals) 0)
362 ;; We need the initial value to be a stretchy vector,
363 ;; so scribble over it just to make sure
364 (let ((,vvals (make-array 1
365 :fill-pointer t :adjustable t
366 :initial-contents (list ,value))))
367 (write-slot ,object ,slot ,writer ,vvals))
368 (vector-push-extend ,value ,vvals))))))
369 (labels ((deserialize (type trace end end-tag)
370 (declare (type fixnum end end-tag))
371 (let* ((message (find-message trace type))
373 (make-instance (or (proto-alias-for message) (proto-class message)))))
374 ;; All the slots into which we store a repeated element
375 ;; These will be reversed at the end of deserialization
378 (multiple-value-bind (tag idx)
379 (if (i< index end) (decode-uint32 buffer index) (values 0 index))
380 ;; We're done if we've gotten to the end index or
381 ;; we see an end tag that matches a previous group's start tag
382 ;; Note that the default end tag is 0, which is also an end of
383 ;; message marker (there can never be "real" zero tags because
384 ;; field indices start at 1)
386 (when (i= tag end-tag)
387 ;; Reverse the repeated slots
388 (dolist (field rslots)
389 (let ((slot (proto-value field))
390 (reader (proto-reader field))
391 (writer (proto-writer field)))
392 (write-slot object slot writer
393 (nreverse (read-slot object slot reader)))))
394 (return-from deserialize
395 (values object index)))
396 (let* ((fidx (ilogand (iash tag -3) #x1FFFFFFF))
397 (field (find fidx (proto-fields message) :key #'proto-index))
398 (type (and field (if (eq (proto-class field) 'boolean) :bool (proto-class field))))
399 ;; It's OK for this to be null
400 ;; That means we're parsing some version of a message
401 ;; that has the field, but our current message does not
402 ;; We still have to deserialize everything, though
403 (slot (and field (proto-value field)))
404 (reader (and field (proto-reader field)))
405 (writer (and field (proto-writer field)))
408 ;; If there's no field descriptor for this index, just skip
409 ;; the next element in the buffer having the given wire type
410 (setq index (skip-element buffer index tag))
411 ;;--- Check for mismatched wire type, running past end of buffer, etc
412 (cond ((and field (eq (proto-required field) :repeated))
413 (let ((vectorp (vector-field-p field)))
414 (cond ((and (proto-packed field) (packed-type-p type))
415 (multiple-value-bind (values idx)
416 (deserialize-packed type buffer index)
419 (let ((values (make-array (length values)
420 :fill-pointer t :adjustable t
421 :initial-contents values)))
422 (write-slot object slot writer values))
423 (write-slot object slot writer values))))
425 (multiple-value-bind (val idx)
426 (deserialize-prim type buffer index)
429 (push-slot object slot reader writer val))
431 (pushnew field rslots)
432 ;; This "push" could type-check the entire list if
433 ;; there's a parameterized list type in effect,
434 ;; so you'll want to avoid using such types
435 ;; We'll reverse the slots at the last minute
436 (write-slot object slot writer
437 (cons val (read-slot object slot reader)))))))
438 ((typep (setq msg (and type (or (find-message trace type)
439 (find-enum trace type)
440 (find-type-alias trace type))))
442 (if (eq (proto-message-type msg) :group)
443 (let* ((etag (make-tag $wire-type-end-group fidx))
444 (obj (deserialize type msg length etag)))
446 (push-slot object slot reader writer obj))
448 (pushnew field rslots)
449 (write-slot object slot writer
450 (cons obj (read-slot object slot reader))))))
451 (multiple-value-bind (len idx)
452 (decode-uint32 buffer index)
454 (let ((obj (deserialize type msg (+ index len) 0)))
456 (push-slot object slot reader writer obj))
458 (pushnew field rslots)
459 (write-slot object slot writer
460 (cons obj (read-slot object slot reader)))))))))
461 ((typep msg 'protobuf-enum)
462 (if (proto-packed field)
463 (multiple-value-bind (values idx)
464 (deserialize-packed-enum (proto-values msg) buffer index)
467 (let ((values (make-array (length values)
468 :fill-pointer t :adjustable t
469 :initial-contents values)))
470 (write-slot object slot writer values))
471 (write-slot object slot writer values)))
472 (multiple-value-bind (val idx)
473 (deserialize-enum (proto-values msg) buffer index)
476 (push-slot object slot reader writer val))
478 (pushnew field rslots)
479 (write-slot object slot writer
480 (cons val (read-slot object slot reader))))))))
481 ((typep msg 'protobuf-type-alias)
482 (let ((type (proto-proto-type msg)))
483 (multiple-value-bind (val idx)
484 (deserialize-prim type buffer index)
487 (push-slot object slot reader writer
488 (funcall (proto-deserializer msg) val)))
490 (pushnew field rslots)
491 (write-slot object slot writer
492 (cons (funcall (proto-deserializer msg) val)
493 (read-slot object slot reader)))))))))))
495 (cond ((keywordp type)
496 (multiple-value-bind (val idx)
497 (deserialize-prim type buffer index)
499 (write-slot object slot writer val)))
500 ((typep (setq msg (and type (or (find-message trace type)
501 (find-enum trace type)
502 (find-type-alias trace type))))
504 ;;--- If there's already a value in the slot, merge messages
505 (if (eq (proto-message-type msg) :group)
506 (let* ((etag (make-tag $wire-type-end-group fidx))
507 (obj (deserialize type msg length etag)))
508 (write-slot object slot writer obj))
509 (multiple-value-bind (len idx)
510 (decode-uint32 buffer index)
512 (let ((obj (deserialize type msg (+ index len) 0)))
513 (write-slot object slot writer obj)))))
514 ((typep msg 'protobuf-enum)
515 (multiple-value-bind (val idx)
516 (deserialize-enum (proto-values msg) buffer index)
518 (write-slot object slot writer val)))
519 ((typep msg 'protobuf-type-alias)
520 (let ((type (proto-proto-type msg)))
521 (multiple-value-bind (val idx)
522 (deserialize-prim type buffer index)
524 (write-slot object slot writer
525 (funcall (proto-deserializer msg) val)))))))))))))))
526 (declare (dynamic-extent #'deserialize))
527 (deserialize (proto-class message) message length end-tag)))))
532 ;; Allow clients to add their own methods
533 ;; This is how we address the problem of cycles, e.g. -- if you have an object
534 ;; that may contain cycles, return the size of the "handle" to the object
535 (defgeneric object-size (object type &optional visited)
537 "Computes the size in bytes of the object 'object' of type 'type'.
538 'type' is the Lisp name of a Protobufs message (usually the name of a
539 Lisp class) or a 'protobuf-message'.
540 'visited' is either a hash table used to cache object sizes,
541 or is nil, in which case the objects must have a '%cached-size' slot in them.
542 The return value is the size of the object in bytes."))
544 (defmethod object-size (object type &optional visited)
545 (let ((message (find-message-for-class type)))
547 "There is no Protobuf message having the type ~S" type)
548 (object-size object message visited)))
550 ;; 'visited' is used to cache object sizes
551 ;; The default method uses metadata from the protobuf "schema" for the message
552 (defmethod object-size (object (message protobuf-message) &optional visited)
554 (declare (type fixnum size))
555 (macrolet ((read-slot (object slot reader)
556 ;; Don't do a boundp check, we assume the object is fully populated
557 ;; Unpopulated slots should be "nullable" and will contain nil when empty
559 (funcall ,reader ,object)
560 (slot-value ,object ,slot))))
561 (labels ((do-field (object trace field)
562 ;; We don't do cycle detection here
563 ;; If the client needs it, he can define his own 'object-size'
564 ;; method to clean things up first
565 (let* ((type (if (eq (proto-class field) 'boolean) :bool (proto-class field)))
566 (slot (proto-value field))
567 (reader (proto-reader field))
569 (when (or slot reader)
570 (cond ((eq (proto-required field) :repeated)
571 (cond ((and (proto-packed field) (packed-type-p type))
572 (let ((tag (make-tag type (proto-index field))))
573 (iincf size (packed-size (read-slot object slot reader) type tag))))
575 (let ((tag (make-tag type (proto-index field))))
576 (doseq (v (read-slot object slot reader))
577 (iincf size (prim-size v type tag)))))
578 ((typep (setq msg (and type (or (find-message trace type)
579 (find-enum trace type)
580 (find-type-alias trace type))))
582 (if (eq (proto-message-type msg) :group)
583 (doseq (v (if slot (read-slot object slot reader) (list object)))
584 (let ((tag1 (make-tag $wire-type-start-group (proto-index field)))
585 (tag2 (make-tag $wire-type-end-group (proto-index field))))
586 (iincf size (length32 tag1))
587 (dolist (f (proto-fields msg))
589 (iincf size (length32 tag2))))
590 (doseq (v (if slot (read-slot object slot reader) (list object)))
591 (let ((tag (make-tag $wire-type-string (proto-index field)))
592 (len (or (cached-object-size v visited)
593 (object-size v msg visited))))
594 (iincf size (length32 tag))
595 (iincf size (length32 len))
596 (dolist (f (proto-fields msg))
597 (do-field v msg f))))))
598 ((typep msg 'protobuf-enum)
599 (let ((tag (make-tag $wire-type-varint (proto-index field))))
600 (if (proto-packed field)
601 (iincf size (packed-enum-size (read-slot object slot reader) type tag))
602 (doseq (v (read-slot object slot reader))
603 (iincf size (enum-size v (proto-values msg) tag))))))
604 ((typep msg 'protobuf-type-alias)
605 (let* ((type (proto-proto-type msg))
606 (tag (make-tag type (proto-index field))))
607 (doseq (v (read-slot object slot reader))
608 (let ((v (funcall (proto-serializer msg) v)))
609 (iincf size (prim-size v type tag))))))
611 (undefined-field-type "While computing the size of ~S,"
612 object type field))))
614 (cond ((eq type :bool)
615 (let ((v (cond ((or (eq (proto-required field) :required)
617 (read-slot object slot reader))
618 ((slot-boundp object slot)
619 (read-slot object slot reader))
621 (unless (eq v :unbound)
622 (let ((tag (make-tag :bool (proto-index field))))
623 (iincf size (prim-size v type tag))))))
625 (let ((v (read-slot object slot reader)))
626 (when (and v (not (equal v (proto-default field))))
627 (let ((tag (make-tag type (proto-index field))))
628 (iincf size (prim-size v type tag))))))
629 ((typep (setq msg (and type (or (find-message trace type)
630 (find-enum trace type)
631 (find-type-alias trace type))))
633 (let ((v (if slot (read-slot object slot reader) object)))
635 (if (eq (proto-message-type msg) :group)
636 (let ((tag1 (make-tag $wire-type-start-group (proto-index field)))
637 (tag2 (make-tag $wire-type-end-group (proto-index field))))
638 (iincf size (length32 tag1))
639 (dolist (f (proto-fields msg))
641 (iincf size (length32 tag2)))
642 (let ((tag (make-tag $wire-type-string (proto-index field)))
643 (len (or (cached-object-size v visited)
644 (object-size v msg visited))))
645 (iincf size (length32 tag))
646 (iincf size (length32 len))
647 (dolist (f (proto-fields msg))
648 (do-field v msg f)))))))
649 ((typep msg 'protobuf-enum)
650 (let ((v (read-slot object slot reader)))
651 (when (and v (not (eql v (proto-default field))))
652 (let ((tag (make-tag $wire-type-varint (proto-index field))))
653 (iincf size (enum-size (read-slot object slot reader) (proto-values msg) tag))))))
654 ((typep msg 'protobuf-type-alias)
655 (let ((v (read-slot object slot reader)))
657 (let* ((v (funcall (proto-serializer msg) v))
658 (type (proto-proto-type msg))
659 (tag (make-tag type (proto-index field))))
660 (iincf size (prim-size v type tag))))))
662 (undefined-field-type "While computing the size of ~S,"
663 object type field)))))))))
664 (declare (dynamic-extent #'do-field))
665 (dolist (field (proto-fields message))
666 (do-field object message field))
667 (setf (cached-object-size object visited) size) ;cache the size
671 ;;; Compile-time generation of serializers
672 ;;; Type-checking is done at the top-level methods specialized on 'symbol',
673 ;;; so we turn off all type checking at the level of these functions
675 ;; Note well: keep this in sync with the main 'serialize-object' method above
676 (defun generate-serializer (message)
677 "Generate a 'serialize-object' method for the given message."
678 (with-gensyms (vobj vbuf vidx vval vclass)
679 (when (null (proto-fields message))
680 (return-from generate-serializer
681 `(defmethod serialize-object
682 (,vobj (,vclass (eql ,message)) ,vbuf &optional (,vidx 0) visited)
683 (declare #.$optimize-serialization)
684 (declare (ignorable ,vobj ,vclass visited)
685 (type (simple-array (unsigned-byte 8)) ,vbuf)
687 (values ,vbuf ,vidx))))
688 (with-collectors ((serializers collect-serializer))
689 (dolist (field (proto-fields message))
690 (let* ((class (if (eq (proto-class field) 'boolean) :bool (proto-class field)))
691 (msg (and class (not (keywordp class))
692 (or (find-message message class)
693 (find-enum message class)
694 (find-type-alias message class))))
695 (reader (cond ((proto-reader field)
696 `(,(proto-reader field) ,vobj))
698 `(slot-value ,vobj ',(proto-value field)))))
699 (index (proto-index field)))
701 (cond ((eq (proto-required field) :repeated)
702 (let* ((vectorp (vector-field-p field))
703 (iterator (if vectorp 'dovector 'dolist)))
704 (cond ((and (proto-packed field) (packed-type-p class))
706 (let ((tag (make-tag class index)))
707 `(setq ,vidx (serialize-packed ,reader ,class ,tag ,vbuf ,vidx
711 (let ((tag (make-tag class index)))
712 `(,iterator (,vval ,reader)
713 (setq ,vidx (serialize-prim ,vval ,class ,tag ,vbuf ,vidx))))))
714 ((typep msg 'protobuf-message)
716 (if (eq (proto-message-type msg) :group)
717 (let ((tag1 (make-tag $wire-type-start-group index))
718 (tag2 (make-tag $wire-type-end-group index)))
719 `(,iterator (,vval ,reader)
720 (let ((len (cached-object-size ,vval visited)))
721 (setq ,vidx (encode-uint32 ,tag1 ,vbuf ,vidx))
722 (serialize-object ,vval ,msg ,vbuf ,vidx visited)
724 (setq ,vidx (encode-uint32 ,tag2 ,vbuf ,vidx)))))
725 (let ((tag (make-tag $wire-type-string index)))
726 `(,iterator (,vval ,reader)
727 (let ((len (cached-object-size ,vval visited)))
728 (setq ,vidx (encode-uint32 ,tag ,vbuf ,vidx))
729 (setq ,vidx (encode-uint32 len ,vbuf ,vidx))
730 (serialize-object ,vval ,msg ,vbuf ,vidx visited)
731 (iincf ,vidx len)))))))
732 ((typep msg 'protobuf-enum)
734 (let ((tag (make-tag $wire-type-varint index)))
735 (if (proto-packed field)
736 `(setq ,vidx (serialize-packed-enum ,reader '(,@(proto-values msg)) ,tag ,vbuf ,vidx
739 `(,iterator (,vval ,reader)
740 (setq ,vidx (serialize-enum ,vval '(,@(proto-values msg)) ,tag ,vbuf ,vidx)))))))
741 ((typep msg 'protobuf-type-alias)
743 (let* ((class (proto-proto-type msg))
744 (tag (make-tag class (proto-index field))))
745 `(,iterator (,vval ,reader)
746 (let ((,vval (funcall #',(proto-serializer msg) ,vval)))
747 (setq ,vidx (serialize-prim ,vval ,class ,tag ,vbuf ,vidx)))))))
749 (undefined-field-type "While generating 'serialize-object' for ~S,"
750 message class field)))))
752 (cond ((keywordp class)
754 (let ((tag (make-tag class index)))
756 (if (or (eq (proto-required field) :required)
757 (null (proto-value field)))
758 `(let ((,vval ,reader))
759 (setq ,vidx (serialize-prim ,vval ,class ,tag ,vbuf ,vidx)))
760 `(let ((,vval (cond ((slot-boundp ,vobj ',(proto-value field))
763 (unless (eq ,vval :unbound)
764 (setq ,vidx (serialize-prim ,vval ,class ,tag ,vbuf ,vidx)))))
765 (if (empty-default-p field)
766 `(let ((,vval ,reader))
768 (setq ,vidx (serialize-prim ,vval ,class ,tag ,vbuf ,vidx))))
769 `(let ((,vval ,reader))
770 (when (and ,vval (not (equal ,vval ',(proto-default field))))
771 (setq ,vidx (serialize-prim ,vval ,class ,tag ,vbuf ,vidx)))))))))
772 ((typep msg 'protobuf-message)
774 (if (eq (proto-message-type msg) :group)
775 (let ((tag1 (make-tag $wire-type-start-group index))
776 (tag2 (make-tag $wire-type-end-group index)))
777 `(let ((,vval ,reader))
779 (let ((len (cached-object-size ,vval visited)))
780 (setq ,vidx (encode-uint32 ,tag1 ,vbuf ,vidx))
781 (serialize-object ,vval ,msg ,vbuf ,vidx visited)
783 (setq ,vidx (encode-uint32 ,tag2 ,vbuf ,vidx))))))
784 (let ((tag (make-tag $wire-type-string index)))
785 `(let ((,vval ,reader))
787 (let ((len (cached-object-size ,vval visited)))
788 (setq ,vidx (encode-uint32 ,tag ,vbuf ,vidx))
789 (setq ,vidx (encode-uint32 len ,vbuf ,vidx))
790 (serialize-object ,vval ,msg ,vbuf ,vidx visited)
791 (iincf ,vidx len))))))))
792 ((typep msg 'protobuf-enum)
794 (let ((tag (make-tag $wire-type-varint index)))
795 (if (empty-default-p field)
796 `(let ((,vval ,reader))
798 (setq ,vidx (serialize-enum ,vval '(,@(proto-values msg)) ,tag ,vbuf ,vidx))))
799 `(let ((,vval ,reader))
800 (when (and ,vval (not (eql ,vval ',(proto-default field))))
801 (setq ,vidx (serialize-enum ,vval '(,@(proto-values msg)) ,tag ,vbuf ,vidx))))))))
802 ((typep msg 'protobuf-type-alias)
804 (let* ((class (proto-proto-type msg))
805 (tag (make-tag class (proto-index field))))
806 `(let ((,vval ,reader))
808 (let ((,vval (funcall #',(proto-serializer msg) ,vval)))
809 (setq ,vidx (serialize-prim ,vval ,class ,tag ,vbuf ,vidx))))))))
811 (undefined-field-type "While generating 'serialize-object' for ~S,"
812 message class field))))))))
813 `(defmethod serialize-object
814 (,vobj (,vclass (eql ,message)) ,vbuf &optional (,vidx 0) visited)
815 (declare #.$optimize-serialization)
816 (declare (ignorable visited)
817 (type (simple-array (unsigned-byte 8)) ,vbuf)
820 (values ,vbuf ,vidx)))))
822 ;; Note well: keep this in sync with the main 'deserialize-object' method above
823 (defun generate-deserializer (message)
824 "Generate a 'deserialize-object' method for the given message."
825 (with-gensyms (vclass vbuf vidx vlen vendtag vobj vval)
826 (when (null (proto-fields message))
827 (return-from generate-deserializer
828 `(defmethod deserialize-object
829 ((,vclass (eql ,message)) ,vbuf &optional ,vidx ,vlen (,vendtag 0))
830 (declare #.$optimize-serialization)
831 (declare (ignorable ,vclass ,vbuf ,vlen ,vendtag)
832 (type (simple-array (unsigned-byte 8)) ,vbuf))
833 (let ((,vidx (or ,vidx 0)))
834 (declare (type fixnum ,vidx))
835 (let ((,vobj (make-instance ',(or (proto-alias-for message) (proto-class message)))))
836 (values ,vobj ,vidx))))))
837 (with-collectors ((deserializers collect-deserializer)
838 ;; For tracking repeated slots that will need to be reversed
839 (rslots collect-rslot))
840 (flet ((read-slot (object field)
841 (cond ((proto-reader field)
842 `(,(proto-reader field) ,object))
844 `(slot-value ,object ',(proto-value field)))))
845 (write-slot (object field value)
846 (cond ((proto-writer field)
847 `(,(proto-writer field) ,object ,value))
849 `(setf (slot-value ,object ',(proto-value field)) ,value)))))
850 (dolist (field (proto-fields message))
851 (let* ((class (if (eq (proto-class field) 'boolean) :bool (proto-class field)))
852 (msg (and class (not (keywordp class))
853 (or (find-message message class)
854 (find-enum message class)
855 (find-type-alias message class))))
856 (index (proto-index field)))
857 (cond ((eq (proto-required field) :repeated)
858 (cond ((and (proto-packed field) (packed-type-p class))
859 (collect-deserializer
860 `((,(make-tag class index))
861 (multiple-value-bind (,vval idx)
862 (deserialize-packed ,class ,vbuf ,vidx)
864 ,@(when (vector-field-p field)
865 `((setq ,vval (make-array (length ,vval)
866 :fill-pointer t :adjustable t
867 :initial-contents ,vval))))
868 ,(write-slot vobj field vval)))))
870 (let ((temp (gensym (string (proto-value field)))))
871 (collect-rslot (list field temp))
872 (collect-deserializer
873 `((,(make-tag class index))
874 (multiple-value-bind (,vval idx)
875 (deserialize-prim ,class ,vbuf ,vidx)
877 (push ,vval ,temp))))))
878 ((typep msg 'protobuf-message)
879 (let ((temp (gensym (string (proto-value field)))))
880 (collect-rslot (list field temp))
881 (collect-deserializer
882 (if (eq (proto-message-type msg) :group)
883 `((,(make-tag $wire-type-start-group index))
884 (multiple-value-bind (,vval idx)
885 (deserialize-object ,msg ,vbuf ,vidx ,vlen
886 ,(make-tag $wire-type-end-group index))
889 `((,(make-tag $wire-type-string index))
890 (multiple-value-bind (len idx)
891 (decode-uint32 ,vbuf ,vidx)
893 (multiple-value-bind (,vval idx)
894 (deserialize-object ,msg ,vbuf ,vidx (i+ ,vidx len) 0)
896 (push ,vval ,temp))))))))
897 ((typep msg 'protobuf-enum)
898 (if (proto-packed field)
899 (collect-deserializer
900 `((,(make-tag $wire-type-varint index))
901 (multiple-value-bind (,vval idx)
902 (deserialize-packed-enum '(,@(proto-values msg)) ,vbuf ,vidx)
904 ,(write-slot vobj field vval))))
905 (let ((temp (gensym (string (proto-value field)))))
906 (collect-rslot (list field temp))
907 (collect-deserializer
908 `((,(make-tag $wire-type-varint index))
909 (multiple-value-bind (,vval idx)
910 (deserialize-enum '(,@(proto-values msg)) ,vbuf ,vidx)
912 (push ,vval ,temp)))))))
913 ((typep msg 'protobuf-type-alias)
914 (let ((class (proto-proto-type msg))
915 (temp (gensym (string (proto-value field)))))
916 (collect-rslot (list field temp))
917 (collect-deserializer
918 `((,(make-tag class index))
919 (multiple-value-bind (,vval idx)
920 (deserialize-prim ,class ,vbuf ,vidx)
922 (push (funcall #',(proto-deserializer msg) ,vval) ,temp))))))
924 (undefined-field-type "While generating 'deserialize-object' for ~S,"
925 message class field))))
927 (cond ((keywordp class)
928 (collect-deserializer
929 `((,(make-tag class index))
930 (multiple-value-bind (,vval idx)
931 (deserialize-prim ,class ,vbuf ,vidx)
933 ,(write-slot vobj field vval)))))
934 ((typep msg 'protobuf-message)
935 (collect-deserializer
936 (if (eq (proto-message-type msg) :group)
937 `((,(make-tag $wire-type-start-group index))
938 (multiple-value-bind (,vval idx)
939 (deserialize-object ,msg ,vbuf ,vidx ,vlen
940 ,(make-tag $wire-type-end-group index))
942 ,(write-slot vobj field vval)))
943 `((,(make-tag $wire-type-string index))
944 (multiple-value-bind (len idx)
945 (decode-uint32 ,vbuf ,vidx)
947 (multiple-value-bind (,vval idx)
948 (deserialize-object ,msg ,vbuf ,vidx (i+ ,vidx len) 0)
950 ,(write-slot vobj field vval)))))))
951 ((typep msg 'protobuf-enum)
952 (collect-deserializer
953 `((,(make-tag $wire-type-varint index))
954 (multiple-value-bind (,vval idx)
955 (deserialize-enum '(,@(proto-values msg)) ,vbuf ,vidx)
957 ,(write-slot vobj field vval)))))
958 ((typep msg 'protobuf-type-alias)
959 (let ((class (proto-proto-type msg)))
960 (collect-deserializer
961 `((,(make-tag class index))
962 (multiple-value-bind (,vval idx)
963 (deserialize-prim ,class ,vbuf ,vidx)
964 (let ((,vval (funcall #',(proto-deserializer msg) ,vval)))
966 ,(write-slot vobj field vval)))))))
968 (undefined-field-type "While generating 'deserialize-object' for ~S,"
969 message class field))))))))
970 (let* ((rslots (delete-duplicates rslots :key #'first))
971 (rfields (mapcar #'first rslots))
972 (rtemps (mapcar #'second rslots)))
973 `(defmethod deserialize-object
974 ((,vclass (eql ,message)) ,vbuf &optional ,vidx ,vlen (,vendtag 0))
975 (declare #.$optimize-serialization)
976 (declare (type (simple-array (unsigned-byte 8)) ,vbuf))
977 (let ((,vidx (or ,vidx 0))
978 (,vlen (or ,vlen (length ,vbuf))))
979 (declare (type fixnum ,vidx ,vlen))
980 (let ((,vobj (make-instance ',(or (proto-alias-for message) (proto-class message))))
981 ;; Bind the temporary variables that hold repeated slots
984 (multiple-value-bind (tag idx)
985 (if (i< ,vidx ,vlen) (decode-uint32 ,vbuf ,vidx) (values 0 ,vidx))
987 (when (i= tag ,vendtag)
988 ;; Set the (un)reversed values of the repeated slots
989 ,@(loop for field in rfields
991 as slot = (proto-value field)
992 as writer = (proto-writer field)
993 collect (cond ((vector-field-p field)
995 `(funcall ,writer ,vobj (make-array (length ,temp)
996 :fill-pointer t :adjustable t
997 :initial-contents (nreverse ,temp)))
998 `(setf (slot-value ,vobj ',slot) (make-array (length ,temp)
999 :fill-pointer t :adjustable t
1000 :initial-contents (nreverse ,temp)))))
1003 `(funcall ,writer ,vobj (nreverse ,temp))
1004 `(setf (slot-value ,vobj ',slot) (nreverse ,temp))))))
1005 (return-from deserialize-object
1006 (values ,vobj ,vidx)))
1010 (setq ,vidx (skip-element ,vbuf ,vidx tag)))))))))))))
1012 ;; Note well: keep this in sync with the main 'object-size' method above
1013 (defun generate-object-size (message)
1014 "Generate an 'object-size' method for the given message."
1015 (with-gensyms (vobj vsize vval vclass)
1016 (when (null (proto-fields message))
1017 (return-from generate-object-size
1018 `(defmethod object-size
1019 (,vobj (,vclass (eql ,message)) &optional visited)
1020 (declare #.$optimize-serialization)
1021 (setf (cached-object-size ,vobj visited) 0)
1023 (with-collectors ((sizers collect-sizer))
1024 (dolist (field (proto-fields message))
1025 (let* ((class (if (eq (proto-class field) 'boolean) :bool (proto-class field)))
1026 (msg (and class (not (keywordp class))
1027 (or (find-message message class)
1028 (find-enum message class)
1029 (find-type-alias message class))))
1030 (reader (cond ((proto-reader field)
1031 `(,(proto-reader field) ,vobj))
1032 ((proto-value field)
1033 `(slot-value ,vobj ',(proto-value field)))))
1034 (index (proto-index field)))
1036 (cond ((eq (proto-required field) :repeated)
1037 (let* ((vectorp (vector-field-p field))
1038 (iterator (if vectorp 'dovector 'dolist)))
1039 (cond ((and (proto-packed field) (packed-type-p class))
1041 (let ((tag (make-tag class index)))
1042 `(iincf ,vsize (packed-size ,reader ,class ,tag ,vectorp)))))
1045 (let ((tag (make-tag class index)))
1046 `(,iterator (,vval ,reader)
1047 (iincf ,vsize (prim-size ,vval ,class ,tag))))))
1048 ((typep msg 'protobuf-message)
1050 (if (eq (proto-message-type msg) :group)
1051 (let ((tag1 (make-tag $wire-type-start-group index))
1052 (tag2 (make-tag $wire-type-end-group index)))
1053 `(,iterator (,vval ,reader)
1054 (let ((len (or (cached-object-size ,vval visited)
1055 (object-size ,vval ,msg visited))))
1056 (iincf ,vsize (length32 ,tag1))
1058 (iincf ,vsize ,tag2))))
1059 (let ((tag (make-tag $wire-type-string index)))
1060 `(,iterator (,vval ,reader)
1061 (let ((len (or (cached-object-size ,vval visited)
1062 (object-size ,vval ,msg visited))))
1063 (iincf ,vsize (length32 ,tag))
1064 (iincf ,vsize (length32 len))
1065 (iincf ,vsize len)))))))
1066 ((typep msg 'protobuf-enum)
1067 (let ((tag (make-tag $wire-type-varint index)))
1069 (if (proto-packed field)
1070 `(iincf ,vsize (packed-enum-size ,reader '(,@(proto-values msg)) ,tag))
1071 `(,iterator (,vval ,reader)
1072 (iincf ,vsize (enum-size ,vval '(,@(proto-values msg)) ,tag)))))))
1073 ((typep msg 'protobuf-type-alias)
1075 (let* ((class (proto-proto-type msg))
1076 (tag (make-tag class index)))
1077 `(,iterator (,vval ,reader)
1078 (let ((,vval (funcall #',(proto-serializer msg) ,vval)))
1079 (iincf ,vsize (prim-size ,vval ,class ,tag)))))))
1081 (undefined-field-type "While generating 'object-size' for ~S,"
1082 message class field)))))
1084 (cond ((keywordp class)
1085 (let ((tag (make-tag class index)))
1087 (if (eq class :bool)
1088 (if (or (eq (proto-required field) :required)
1089 (null (proto-value field)))
1090 `(let ((,vval ,reader))
1091 (declare (ignorable ,vval))
1092 (iincf ,vsize (prim-size ,vval ,class ,tag)))
1093 `(let ((,vval (cond ((slot-boundp ,vobj ',(proto-value field))
1096 (unless (eq ,vval :unbound)
1097 (iincf ,vsize (prim-size ,vval ,class ,tag)))))
1098 (if (empty-default-p field)
1099 `(let ((,vval ,reader))
1101 (iincf ,vsize (prim-size ,vval ,class ,tag))))
1102 `(let ((,vval ,reader))
1103 (when (and ,vval (not (equal ,vval ',(proto-default field))))
1104 (iincf ,vsize (prim-size ,vval ,class ,tag)))))))))
1105 ((typep msg 'protobuf-message)
1107 (if (eq (proto-message-type msg) :group)
1108 (let ((tag1 (make-tag $wire-type-start-group index))
1109 (tag2 (make-tag $wire-type-end-group index)))
1110 `(let ((,vval ,reader))
1112 (let ((len (or (cached-object-size ,vval visited)
1113 (object-size ,vval ,msg visited))))
1114 (iincf ,vsize (length32 ,tag1))
1116 (iincf ,vsize (length32 ,tag2))))))
1117 (let ((tag (make-tag $wire-type-string index)))
1118 `(let ((,vval ,reader))
1120 (let ((len (or (cached-object-size ,vval visited)
1121 (object-size ,vval ,msg visited))))
1122 (iincf ,vsize (length32 ,tag))
1123 (iincf ,vsize (length32 len))
1124 (iincf ,vsize len))))))))
1125 ((typep msg 'protobuf-enum)
1126 (let ((tag (make-tag $wire-type-varint index)))
1128 (if (empty-default-p field)
1129 `(let ((,vval ,reader))
1131 (iincf ,vsize (enum-size ,vval '(,@(proto-values msg)) ,tag))))
1132 `(let ((,vval ,reader))
1133 (when (and ,vval (not (eql ,vval ',(proto-default field))))
1134 (iincf ,vsize (enum-size ,vval '(,@(proto-values msg)) ,tag))))))))
1135 ((typep msg 'protobuf-type-alias)
1137 (let* ((class (proto-proto-type msg))
1138 (tag (make-tag class index)))
1139 `(let ((,vval ,reader))
1141 (iincf ,vsize (prim-size (funcall #',(proto-serializer msg) ,vval)
1144 (undefined-field-type "While generating 'object-size' for ~S,"
1145 message class field))))))))
1146 `(defmethod object-size
1147 (,vobj (,vclass (eql ,message)) &optional visited)
1148 (declare #.$optimize-serialization)
1149 (declare (ignorable visited))
1151 (declare (type fixnum ,vsize))
1153 (setf (cached-object-size ,vobj visited) ,vsize)