]> asedeno.scripts.mit.edu Git - cl-protobufs.git/blobdiff - wire-format.lisp
Tweak the Proto code generator so that we can attempt
[cl-protobufs.git] / wire-format.lisp
index a817fc99e8152242c25140fb4a05292ab40c1e83..5b10f79d6cc1cd17e3011d5060145dd497e8b8a6 100644 (file)
@@ -18,6 +18,8 @@
 (defconstant $wire-type-varint 0)
 (defconstant $wire-type-64bit  1)
 (defconstant $wire-type-string 2)
+(defconstant $wire-type-start-group 3)          ;supposedly obsolete
+(defconstant $wire-type-end-group   4)          ;supposedly obsolete
 (defconstant $wire-type-32bit  5)
 
 (defun make-tag (type index)
 
 
 (defun zig-zag-encode32 (val)
+  (declare (optimize (speed 3) (safety 0) (debug 0)))
   (declare (type (signed-byte 32) val))
-  (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
-    (logxor (ash val 1) (ash val -31))))
+  (logxor (ash val 1) (ash val -31)))
 
 (defun zig-zag-encode64 (val)
+  (declare (optimize (speed 3) (safety 0) (debug 0)))
   (declare (type (signed-byte 64) val))
-  (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
-    (logxor (ash val 1) (ash val -63))))
+  (logxor (ash val 1) (ash val -63)))
 
 (define-compiler-macro zig-zag-encode32 (&whole form val)
   (if (atom val)
     form))
 
 (defun zig-zag-decode32 (val)
-  (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
-    (logxor (ash val -1) (- (logand val 1)))))
+  (declare (optimize (speed 3) (safety 0) (debug 0)))
+  (logxor (ash val -1) (- (logand val 1))))
 
 (defun zig-zag-decode64 (val)
-  (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
-    (logxor (ash val -1) (- (logand val 1)))))
+  (declare (optimize (speed 3) (safety 0) (debug 0)))
+  (logxor (ash val -1) (- (logand val 1))))
 
 (define-compiler-macro zig-zag-decode32 (&whole form val)
   (if (atom val)
 (defun serialize-prim (val type tag buffer index)
   "Serializes a Protobufs primitive (scalar) value into the buffer at the given index.
    The value is given by 'val', the primitive type by 'type'.
-   Modifies the buffer in place, and returns the new index into the buffer."
+   Modifies the buffer in place, and returns the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
   (declare (type (simple-array (unsigned-byte 8)) buffer)
            (type (unsigned-byte 32) tag)
            (type fixnum index))
         ((:sfixed64)
          (encode-sfixed64 val buffer idx))
         ((:string)
-         (encode-octets (babel:string-to-octets val :encoding :utf-8) buffer idx))
+         (encode-string val buffer idx))
         ((:bytes)
          (encode-octets val buffer idx))
         ((:bool)
          (encode-double val buffer idx))
         ;; A few of our homegrown types
         ((:symbol)
-         (let ((val (format nil "~A:~A" (package-name (symbol-package val)) (symbol-name val))))
-           ;; Call 'string' in case we are trying to serialize a symbol name
-           (encode-octets (babel:string-to-octets val :encoding :utf-8) buffer idx)))
+         (let ((val (if (keywordp val)
+                      (string val)
+                      ;; Non-keyword symbols are consy, avoid them if possible
+                      (format nil "~A:~A" (package-name (symbol-package val)) (symbol-name val)))))
+           (encode-string val buffer idx)))
         ((:date :time :datetime :timestamp)
          (encode-uint64 val buffer idx))))))
 
   (if (member type '(:int32 :uint32 :int64 :uint64 :sint32 :sint64
                      :fixed32 :sfixed32 :fixed64 :sfixed64
                      :string :bytes :bool :float :double))
-    `(locally (declare (optimize (speed 3) (safety 0) (debug 0)))
+    `(locally (declare (optimize (speed 3) (safety 0) (debug 0))
+                       (type (simple-array (unsigned-byte 8)) ,buffer)
+                       ;; 'tag' is a constant, no need to declare its type
+                       (type fixnum ,index))
        (let ((idx (encode-uint32 ,tag ,buffer ,index)))
          (declare (type fixnum idx))
          ,(ecase type
             ((:sfixed64)
              `(encode-sfixed64 ,val ,buffer idx))
             ((:string)
-             `(encode-octets (babel:string-to-octets ,val :encoding :utf-8) ,buffer idx))
+             `(encode-string ,val ,buffer idx))
             ((:bytes)
              `(encode-octets ,val ,buffer idx))
             ((:bool)
 (defun serialize-packed (values type tag buffer index)
   "Serializes a set of packed values into the buffer at the given index.
    The values are given by 'values', the primitive type by 'type'.
-   Modifies the buffer in place, and returns the new index into the buffer."
+   Modifies the buffer in place, and returns the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
   (declare (type (simple-array (unsigned-byte 8)) buffer)
            (type (unsigned-byte 32) tag)
            (type fixnum index))
   (if (member type '(:int32 :uint32 :int64 :uint64 :sint32 :sint64
                      :fixed32 :sfixed32 :fixed64 :sfixed64
                      :float :double))
-    `(locally (declare (optimize (speed 3) (safety 0) (debug 0)))
+    `(locally (declare (optimize (speed 3) (safety 0) (debug 0))
+                       (type (simple-array (unsigned-byte 8)) ,buffer)
+                       (type fixnum ,index))
        (let ((idx (encode-uint32 ,tag ,buffer ,index)))
          (declare (type fixnum idx))
          (multiple-value-bind (full-len len)
 (defun serialize-enum (val values tag buffer index)
   "Serializes a Protobufs enum value into the buffer at the given index.
    The value is given by 'val', the enum values are in 'values'.
-   Modifies the buffer in place, and returns the new index into the buffer."
+   Modifies the buffer in place, and returns the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
   (declare (type (simple-array (unsigned-byte 8)) buffer)
            (type (unsigned-byte 32) tag)
            (type fixnum index))
 (defun deserialize-prim (type buffer index)
   "Deserializes the next object of primitive type 'type'.
    Deserializes from the byte vector 'buffer' starting at 'index'.
-   Returns the value and and the new index into the buffer."
+   Returns the value and and the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
   (declare (type (simple-array (unsigned-byte 8)) buffer)
            (type fixnum index))
   (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
       ((:sfixed64)
        (decode-sfixed64 buffer index))
       ((:string)
-       (multiple-value-bind (val idx)
-           (decode-octets buffer index)
-         (values (babel:octets-to-string val :encoding :utf-8) idx)))
+       (decode-string buffer index))
       ((:bytes)
        (decode-octets buffer index))
       ((:bool)
        (decode-double buffer index))
       ;; A few of our homegrown types
       ((:symbol)
+       ;; Note that this is consy, avoid it if possible
        (multiple-value-bind (val idx)
-           (decode-octets buffer index)
-         (let* ((val   (babel:octets-to-string val :encoding :utf-8))
-                (colon (position #\: val))
-                (pkg   (subseq val 0 colon))
-                (sym   (subseq val (i+ colon 1))))
-           (values (intern sym pkg) idx))))
+           (decode-string buffer index)
+         (values (make-lisp-symbol val) idx)))
       ((:date :time :datetime :timestamp)
        (decode-uint64 buffer index)))))
 
   (if (member type '(:int32 :uint32 :int64 :uint64 :sint32 :sint64
                      :fixed32 :sfixed32 :fixed64 :sfixed64
                      :string :bytes :bool :float :double))
-    `(locally (declare (optimize (speed 3) (safety 0) (debug 0)))
+    `(locally (declare (optimize (speed 3) (safety 0) (debug 0))
+                       (type (simple-array (unsigned-byte 8)) ,buffer)
+                       (type fixnum ,index))
        ,(ecase type
           ((:int32 :uint32)
            `(decode-uint32 ,buffer ,index))
           ((:sfixed64)
            `(decode-sfixed64 ,buffer ,index))
           ((:string)
-           `(multiple-value-bind (val idx)
-                (decode-octets ,buffer ,index)
-              (values (babel:octets-to-string val :encoding :utf-8) idx)))
+           `(decode-string ,buffer ,index))
           ((:bytes)
            `(decode-octets ,buffer ,index))
           ((:bool)
 (defun deserialize-packed (type buffer index)
   "Deserializes the next packed values of type 'type'.
    Deserializes from the byte vector 'buffer' starting at 'index'.
-   Returns the value and and the new index into the buffer."
+   Returns the value and and the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
   (declare (type (simple-array (unsigned-byte 8)) buffer)
            (type fixnum index))
   (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
               (collect-value val)
               (setq idx nidx))))))))
 
+(define-compiler-macro deserialize-packed (&whole form type buffer index)
+  (if (member type '(:int32 :uint32 :int64 :uint64 :sint32 :sint64
+                     :fixed32 :sfixed32 :fixed64 :sfixed64
+                     :float :double))
+    `(locally (declare (optimize (speed 3) (safety 0) (debug 0))
+                       (type (simple-array (unsigned-byte 8)) ,buffer)
+                       (type fixnum ,index))
+       (multiple-value-bind (len idx)
+           (decode-uint32 .buffer ,index)
+         (declare (type (unsigned-byte 32) len)
+                  (type fixnum idx))
+         (let ((end (i+ idx len)))
+           (declare (type (unsigned-byte 32) end))
+           (with-collectors ((values collect-value))
+             (loop
+               (when (>= idx end)
+                 (return-from deserialize-packed (values values idx)))
+               (multiple-value-bind (val nidx)
+                   ,(ecase type
+                      ((:int32 :uint32)
+                       `(decode-uint32 ,buffer idx))
+                      ((:int64 :uint64)
+                       `(decode-uint64 ,buffer idx))
+                      ((:sint32)
+                       `(multiple-value-bind (val idx)
+                            (decode-uint32 ,buffer idx)
+                          (values (zig-zag-decode32 val) idx)))
+                      ((:sint64)
+                       `(multiple-value-bind (val idx)
+                            (decode-uint64 ,buffer idx)
+                          (values (zig-zag-decode64 val) idx)))
+                      ((:fixed32)
+                       `(decode-fixed32 ,buffer idx))
+                      ((:sfixed32)
+                       `(decode-sfixed32 ,buffer idx))
+                      ((:fixed64)
+                       `(decode-fixed64 ,buffer idx))
+                      ((:sfixed64)
+                       `(decode-sfixed64 ,buffer idx))
+                      ((:float)
+                       `(decode-single ,buffer idx))
+                      ((:double)
+                       `(decode-double ,buffer idx)))
+                 (collect-value val)
+                 (setq idx nidx)))))))
+    form))
+
 (defun deserialize-enum (values buffer index)
   "Deserializes the next enum value take from 'values'.
    Deserializes from the byte vector 'buffer' starting at 'index'.
-   Returns the value and and the new index into the buffer."
+   Returns the value and and the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
   (declare (type (simple-array (unsigned-byte 8)) buffer)
            (type fixnum index))
   (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
 ;;; Object sizing
 
 (defun prim-size (val type tag)
-  "Returns the size in bytes that the primitive object will take when serialized."
+  "Returns the size in bytes that the primitive object will take when serialized.
+   Watch out, this function turns off most type checking."
   (declare (type (unsigned-byte 32) tag))
   (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
     (ecase type
        (let ((len (babel:string-size-in-octets val :encoding :utf-8)))
          (i+ (length32 tag) (length32 len) len)))
       ((:bytes)
-       (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
-         (let ((len (length val)))
-           (i+ (length32 tag) (length32 len) len))))
+       (let ((len (length val)))
+         (i+ (length32 tag) (length32 len) len)))
       ((:bool)
        (i+ (length32 tag) 1))
       ((:float)
        (i+ (length32 tag) 8))
       ;; A few of our homegrown types
       ((:symbol)
-       (let* ((len (i+ (length (package-name (symbol-package val))) 1 (length (symbol-name val)))))
+       (let ((len (if (keywordp val)
+                    (length (symbol-name val))
+                    (i+ (length (package-name (symbol-package val))) 1 (length (symbol-name val))))))
          (i+ (length32 tag) (length32 len) len)))
       ((:date :time :datetime :timestamp)
        (i+ (length32 tag) 8)))))
     form))
 
 (defun packed-size (values type tag)
-  "Returns the size in bytes that the packed object will take when serialized."
+  "Returns the size in bytes that the packed object will take when serialized.
+   Watch out, this function turns off most type checking."
   (declare (type (unsigned-byte 32) tag))
   (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
     (let ((len (let ((len 0))
   (if (member type '(:int32 :uint32 :int64 :uint64 :sint32 :sint64
                      :fixed32 :sfixed32 :fixed64 :sfixed64
                      :float :double))
-    `(locally (declare (optimize (speed 3) (safety 0) (debug 0))
-                       (type (unsigned-byte 32) tag))
+    `(locally (declare (optimize (speed 3) (safety 0) (debug 0)))
        (let ((len (let ((len 0))
                     (declare (type fixnum len))
                     (dolist (val ,values len)
                                     ((:float) `4)
                                     ((:double) `8)))))))
          (declare (type (unsigned-byte 32) len))
-         (values (i+ (length32 ,tag) (length32 len) len) len)))
+         (values (i+ (length32 (the (unsigned-byte 32) ,tag)) (length32 len) len) len)))
     form))
 
 (defun enum-size (val values tag)
   "Returns the size in bytes that the enum object will take when serialized."
   (declare (type (unsigned-byte 32) tag))
-  (let ((val (let ((e (find val values :key #'proto-value)))
+  (let ((idx (let ((e (find val values :key #'proto-value)))
                (and e (proto-index e)))))
-    (declare (type (unsigned-byte 32) val))
-    (i+ (length32 tag) (length32 val))))
+    (assert idx () "There is no enum value for ~S" val)
+    (i+ (length32 tag) (length32 idx))))
 
 
 ;;; Raw encoders
 (defun encode-uint32 (val buffer index)
   "Encodes the unsigned 32-bit integer 'val' as a varint into the buffer
    at the given index.
-   Modifies the buffer, and returns the new index into the buffer."
+   Modifies the buffer, and returns the new index into the buffer.
+   Watch out, this function turns off all type checking and array bounds checking."
+  (declare (optimize (speed 3) (safety 0) (debug 0)))
   (declare (type (unsigned-byte 32) val)
            (type (simple-array (unsigned-byte 8)) buffer)
            (type fixnum index))
-  (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
-    ;; Seven bits at a time, least significant bits first
-    (loop do (let ((bits (ldb #.(byte 7 0) val)))
-               (declare (type (unsigned-byte 8) bits))
-               (setq val (ash val -7))
-               (setf (aref buffer index) (ilogior bits (if (zerop val) 0 128)))
-               (iincf index))
-          until (zerop val)))
+  ;; Seven bits at a time, least significant bits first
+  (loop do (let ((bits (ldb (byte 7 0) val)))
+             (declare (type (unsigned-byte 8) bits))
+             (setq val (ash val -7))
+             (setf (aref buffer index) (ilogior bits (if (zerop val) 0 128)))
+             (iincf index))
+        until (zerop val))
   (values index buffer))                        ;return the buffer to improve 'trace'
 
 (defun encode-uint64 (val buffer index)
   "Encodes the unsigned 64-bit integer 'val' as a varint into the buffer
    at the given index.
-   Modifies the buffer, and returns the new index into the buffer."
+   Modifies the buffer, and returns the new index into the buffer.
+   Watch out, this function turns off all type checking and array bounds checking."
+  (declare (optimize (speed 3) (safety 0) (debug 0)))
   (declare (type (unsigned-byte 64) val)
            (type (simple-array (unsigned-byte 8)) buffer)
            (type fixnum index))
-  (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
-    (loop do (let ((bits (ldb #.(byte 7 0) val)))
-               (declare (type (unsigned-byte 8) bits))
-               (setq val (ash val -7))
-               (setf (aref buffer index) (ilogior bits (if (zerop val) 0 128)))
-               (iincf index))
-          until (zerop val)))
+  (loop do (let ((bits (ldb (byte 7 0) val)))
+             (declare (type (unsigned-byte 8) bits))
+             (setq val (ash val -7))
+             (setf (aref buffer index) (ilogior bits (if (zerop val) 0 128)))
+             (iincf index))
+        until (zerop val))
   (values index buffer))
 
 (defun encode-fixed32 (val buffer index)
   "Encodes the unsigned 32-bit integer 'val' as a fixed int into the buffer
    at the given index.
-   Modifies the buffer, and returns the new index into the buffer."
+   Modifies the buffer, and returns the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
   (declare (type (unsigned-byte 32) val)
            (type (simple-array (unsigned-byte 8)) buffer)
            (type fixnum index))
   (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
     (loop repeat 4 doing
-      (let ((byte (ldb #.(byte 8 0) val)))
+      (let ((byte (ldb (byte 8 0) val)))
         (declare (type (unsigned-byte 8) byte))
         (setq val (ash val -8))
         (setf (aref buffer index) byte)
 (defun encode-fixed64 (val buffer index)
   "Encodes the unsigned 64-bit integer 'val' as a fixed int into the buffer
    at the given index.
-   Modifies the buffer, and returns the new index into the buffer."
+   Modifies the buffer, and returns the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
   (declare (type (unsigned-byte 64) val)
            (type (simple-array (unsigned-byte 8)) buffer)
            (type fixnum index))
   (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
     (loop repeat 8 doing
-      (let ((byte (ldb #.(byte 8 0) val)))
+      (let ((byte (ldb (byte 8 0) val)))
         (declare (type (unsigned-byte 8) byte))
         (setq val (ash val -8))
         (setf (aref buffer index) byte)
 (defun encode-sfixed32 (val buffer index)
   "Encodes the signed 32-bit integer 'val' as a fixed int into the buffer
    at the given index.
-   Modifies the buffer, and returns the new index into the buffer."
+   Modifies the buffer, and returns the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
   (declare (type (signed-byte 32) val)
            (type (simple-array (unsigned-byte 8)) buffer)
            (type fixnum index))
   (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
     (loop repeat 4 doing
-      (let ((byte (ldb #.(byte 8 0) val)))
+      (let ((byte (ldb (byte 8 0) val)))
         (declare (type (unsigned-byte 8) byte))
         (setq val (ash val -8))
         (setf (aref buffer index) byte)
 (defun encode-sfixed64 (val buffer index)
   "Encodes the signed 32-bit integer 'val' as a fixed int into the buffer
    at the given index.
-   Modifies the buffer, and returns the new index into the buffer."
+   Modifies the buffer, and returns the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
   (declare (type (signed-byte 64) val)
            (type (simple-array (unsigned-byte 8)) buffer)
            (type fixnum index))
   (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
     (loop repeat 8 doing
-      (let ((byte (ldb #.(byte 8 0) val)))
+      (let ((byte (ldb (byte 8 0) val)))
         (declare (type (unsigned-byte 8) byte))
         (setq val (ash val -8))
         (setf (aref buffer index) byte)
 
 (defun encode-single (val buffer index)
   "Encodes the single float 'val' into the buffer at the given index.
-   Modifies the buffer, and returns the new index into the buffer."
+   Modifies the buffer, and returns the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
   (declare (type single-float val)
            (type (simple-array (unsigned-byte 8)) buffer)
            (type fixnum index))
   (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
     (let ((bits (single-float-bits val)))
       (loop repeat 4 doing
-        (let ((byte (ldb #.(byte 8 0) bits)))
+        (let ((byte (ldb (byte 8 0) bits)))
           (declare (type (unsigned-byte 8) byte))
           (setq bits (ash bits -8))
           (setf (aref buffer index) byte)
 
 (defun encode-double (val buffer index)
   "Encodes the double float 'val' into the buffer at the given index.
-   Modifies the buffer, and returns the new index into the buffer."
+   Modifies the buffer, and returns the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
   (declare (type double-float val)
            (type (simple-array (unsigned-byte 8)) buffer)
            (type fixnum index))
     (multiple-value-bind (low high)
         (double-float-bits val)
       (loop repeat 4 doing
-        (let ((byte (ldb #.(byte 8 0) low)))
+        (let ((byte (ldb (byte 8 0) low)))
           (declare (type (unsigned-byte 8) byte))
           (setq low (ash low -8))
           (setf (aref buffer index) byte)
           (iincf index)))
       (loop repeat 4 doing
-        (let ((byte (ldb #.(byte 8 0) high)))
+        (let ((byte (ldb (byte 8 0) high)))
           (declare (type (unsigned-byte 8) byte))
           (setq high (ash high -8))
           (setf (aref buffer index) byte)
           (iincf index)))))
   (values index buffer))
 
+(defun encode-string (string buffer index)
+  "Encodes the octets into the buffer at the given index.
+   Modifies the buffer, and returns the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
+  (declare (type (simple-array (unsigned-byte 8)) buffer)
+           (type fixnum index))
+  (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
+    (let* ((octets (babel:string-to-octets string :encoding :utf-8))
+           (len (length octets))
+           (idx (encode-uint32 len buffer index)))
+      (declare (type fixnum len)
+               (type (unsigned-byte 32) idx))
+      (replace buffer octets :start1 idx)
+      (values (i+ idx len) buffer))))
+
 (defun encode-octets (octets buffer index)
   "Encodes the octets into the buffer at the given index.
-   Modifies the buffer, and returns the new index into the buffer."
+   Modifies the buffer, and returns the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
   (declare (type (simple-array (unsigned-byte 8)) buffer)
            (type fixnum index))
   (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
 ;; then return the value and new index into the buffer
 (defun decode-uint32 (buffer index)
   "Decodes the next 32-bit varint integer in the buffer at the given index.
-   Returns both the decoded value and the new index into the buffer."
+   Returns both the decoded value and the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
+  (declare (optimize (speed 3) (safety 0) (debug 0)))
   (declare (type (simple-array (unsigned-byte 8)) buffer)
            (type fixnum index))
-  (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
-    ;; Seven bits at a time, least significant bits first
-    (loop with val = 0
-          for places fixnum upfrom 0 by 7
-          for byte fixnum = (prog1 (aref buffer index) (iincf index))
-          do (setq val (logior val (ash (ldb #.(byte 7 0) byte) places)))
-          until (i< byte 128)
-          finally (progn
-                    (assert (< val #.(ash 1 32)) ()
-                            "The value ~D is longer than 32 bits" val)
-                    (return (values val index))))))
+  ;; Seven bits at a time, least significant bits first
+  (loop with val = 0
+        for places fixnum upfrom 0 by 7
+        for byte fixnum = (prog1 (aref buffer index) (iincf index))
+        do (setq val (logior val (ash (ldb (byte 7 0) byte) places)))
+        until (i< byte 128)
+        finally (progn
+                  (assert (< val #.(ash 1 32)) ()
+                          "The value ~D is longer than 32 bits" val)
+                  (return (values val index)))))
 
 (defun decode-uint64 (buffer index)
   "Decodes the next 64-bit varint integer in the buffer at the given index.
-   Returns both the decoded value and the new index into the buffer."
+   Returns both the decoded value and the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
+  (declare (optimize (speed 3) (safety 0) (debug 0)))
   (declare (type (simple-array (unsigned-byte 8)) buffer)
            (type fixnum index))
-  (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
-    ;; Seven bits at a time, least significant bits first
-    (loop with val = 0
-          for places fixnum upfrom 0 by 7
-          for byte fixnum = (prog1 (aref buffer index) (iincf index))
-          do (setq val (logior val (ash (ldb #.(byte 7 0) byte) places)))
-          until (i< byte 128)
-          finally (return (values val index)))))
+  ;; Seven bits at a time, least significant bits first
+  (loop with val = 0
+        for places fixnum upfrom 0 by 7
+        for byte fixnum = (prog1 (aref buffer index) (iincf index))
+        do (setq val (logior val (ash (ldb (byte 7 0) byte) places)))
+        until (i< byte 128)
+        finally (return (values val index))))
 
 (defun decode-fixed32 (buffer index)
   "Decodes the next 32-bit unsigned fixed integer in the buffer at the given index.
-   Returns both the decoded value and the new index into the buffer."
+   Returns both the decoded value and the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
   (declare (type (simple-array (unsigned-byte 8)) buffer)
            (type fixnum index))
   (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
 
 (defun decode-sfixed32 (buffer index)
   "Decodes the next 32-bit signed fixed integer in the buffer at the given index.
-   Returns both the decoded value and the new index into the buffer."
+   Returns both the decoded value and the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
   (declare (type (simple-array (unsigned-byte 8)) buffer)
            (type fixnum index))
   (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
             for places fixnum upfrom 0 by 8
             for byte fixnum = (prog1 (aref buffer index) (iincf index))
             do (setq val (logior val (ash byte places))))
-      (when (i= (ldb #.(byte 1 31) val) 1)              ;sign bit set, so negative value
+      (when (i= (ldb (byte 1 31) val) 1)              ;sign bit set, so negative value
         (decf val #.(ash 1 32)))
       (values val index))))
 
 (defun decode-fixed64 (buffer index)
   "Decodes the next unsigned 64-bit fixed integer in the buffer at the given index.
-   Returns both the decoded value and the new index into the buffer."
+   Returns both the decoded value and the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
   (declare (type (simple-array (unsigned-byte 8)) buffer)
            (type fixnum index))
   (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
 
 (defun decode-sfixed64 (buffer index)
   "Decodes the next signed 64-bit fixed integer in the buffer at the given index.
-   Returns both the decoded value and the new index into the buffer."
+   Returns both the decoded value and the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
   (declare (type (simple-array (unsigned-byte 8)) buffer)
            (type fixnum index))
   (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
             for places fixnum upfrom 0 by 8
             for byte fixnum = (prog1 (aref buffer index) (iincf index))
             do (setq val (logior val (ash byte places))))
-      (when (i= (ldb #.(byte 1 63) val) 1)             ;sign bit set, so negative value
+      (when (i= (ldb (byte 1 63) val) 1)             ;sign bit set, so negative value
         (decf val #.(ash 1 64)))
       (values val index))))
 
 (defun decode-single (buffer index)
   "Decodes the next single float in the buffer at the given index.
-   Returns both the decoded value and the new index into the buffer."
+   Returns both the decoded value and the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
   (declare (type (simple-array (unsigned-byte 8)) buffer)
            (type fixnum index))
   (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
             for places fixnum upfrom 0 by 8
             for byte fixnum = (prog1 (aref buffer index) (iincf index))
             do (setq bits (logior bits (ash byte places))))
-      (when (i= (ldb #.(byte 1 31) bits) 1)             ;sign bit set, so negative value
+      (when (i= (ldb (byte 1 31) bits) 1)             ;sign bit set, so negative value
         (decf bits #.(ash 1 32)))
       (values (make-single-float bits) index))))
 
 (defun decode-double (buffer index)
   "Decodes the next double float in the buffer at the given index.
-   Returns both the decoded value and the new index into the buffer."
+   Returns both the decoded value and the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
   (declare (type (simple-array (unsigned-byte 8)) buffer)
            (type fixnum index))
   (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
             for byte fixnum = (prog1 (aref buffer index) (iincf index))
             do (setq high (logior high (ash byte places))))
       ;; High bits are signed, but low bits are unsigned
-      (when (i= (ldb #.(byte 1 31) high) 1)             ;sign bit set, so negative value
+      (when (i= (ldb (byte 1 31) high) 1)             ;sign bit set, so negative value
         (decf high #.(ash 1 32)))
       (values (make-double-float low high) index))))
 
+(defun decode-string (buffer index)
+  "Decodes the next UTF-8 encoded string in the buffer at the given index.
+   Returns both the decoded string and the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
+  (declare (type (simple-array (unsigned-byte 8)) buffer)
+           (type fixnum index))
+  (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
+    (multiple-value-bind (len idx)
+        (decode-uint32 buffer index)
+      (declare (type (unsigned-byte 32) len)
+               (type fixnum idx))
+      (values (babel:octets-to-string buffer :start idx :end (i+ idx len) :encoding :utf-8) (i+ idx len)))))
+
 (defun decode-octets (buffer index)
   "Decodes the next octets in the buffer at the given index.
-   Returns both the decoded value and the new index into the buffer."
+   Returns both the decoded value and the new index into the buffer.
+   Watch out, this function turns off most type checking and all array bounds checking."
   (declare (type (simple-array (unsigned-byte 8)) buffer)
            (type fixnum index))
   (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
 
 (defun length32 (val)
   "Returns the length that 'val' will take when encoded as a 32-bit integer."
-  (assert (< val #.(ash 1 32)) ()
-          "The value ~D is longer than 32 bits" val)
-  (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
-    (let ((size 0))
-      (declare (type fixnum size))
-      (loop do (progn
-                 (setq val (ash val -7))
-                 (iincf size))
-            until (zerop val))
-      size)))
+  (declare (optimize (speed 3) (safety 0) (debug 0)))
+  (let ((size 0))
+    (declare (type fixnum size))
+    (loop do (progn
+               (setq val (ash val -7))
+               (iincf size))
+          until (zerop val))
+    size))
 
 (defun length64 (val)
   "Returns the length that 'val' will take when encoded as a 64-bit integer."
-  (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
-    (let ((size 0))
-      (declare (type fixnum size))
-      (loop do (progn
-                 (setq val (ash val -7))
-                 (iincf size))
-            until (zerop val))
-      size)))
+  (declare (optimize (speed 3) (safety 0) (debug 0)))
+  (let ((size 0))
+    (declare (type fixnum size))
+    (loop do (progn
+               (setq val (ash val -7))
+               (iincf size))
+          until (zerop val))
+    size))
 
 
 ;;; Skipping elements
 
-(defun skip-element (buffer index wire-type)
+(defun skip-element (buffer index tag)
   "Skip an element in the buffer at the index of the given wire type.
-   Returns the new index in the buffer."
+   Returns the new index in the buffer.
+   Watch out, this function turns off all type checking and all array bounds checking."
+  (declare (optimize (speed 3) (safety 0) (debug 0)))
   (declare (type (simple-array (unsigned-byte 8)) buffer)
            (type fixnum index)
-           (type (unsigned-byte 32) wire-type))
-  (locally (declare (optimize (speed 3) (safety 0) (debug 0)))
-    (case wire-type
-      (($wire-type-varint)
-       (loop for byte fixnum = (prog1 (aref buffer index) (iincf index))
-             until (i< byte 128))
-       index)
-      (($wire-type-string)
-       (multiple-value-bind (len idx)
-           (decode-uint32 buffer index)
-         (declare (type (unsigned-byte 32) len)
-                  (type fixnum idx))
-         (i+ idx len)))
-      (($wire-type-32bit)
-       (i+ index 4))
-      (($wire-type-64bit)
-       (i+ index 8)))))
+           (type (unsigned-byte 32) tag))
+  (case (ilogand tag #x7)
+    (($wire-type-varint)
+     (loop for byte fixnum = (prog1 (aref buffer index) (iincf index))
+           until (i< byte 128))
+     index)
+    (($wire-type-string)
+     (multiple-value-bind (len idx)
+         (decode-uint32 buffer index)
+       (declare (type (unsigned-byte 32) len)
+                (type fixnum idx))
+       (i+ idx len)))
+    (($wire-type-32bit)
+     (i+ index 4))
+    (($wire-type-64bit)
+     (i+ index 8))
+    (($wire-type-start-group)
+     (loop (multiple-value-bind (new-tag idx)
+               (decode-uint32 buffer index)
+             (cond ((not (i= (ilogand new-tag #x7) $wire-type-start-group))
+                    (setq index (skip-element buffer idx new-tag)))
+                   ;; Clever test for matching end group number
+                   ((i= (i- tag $wire-type-start-group) (i- new-tag $wire-type-end-group))
+                    (return idx))
+                   (t
+                    (assert (i= (i- tag $wire-type-start-group) (i- new-tag $wire-type-end-group)) ()
+                            "Couldn't find a matching end group tag"))))))
+    (t index)))