j3pic / lisp-binary

A library to easily read and write complex binary formats.
GNU General Public License v3.0
91 stars 15 forks source link

Conditions on field value #74

Closed joelreymont closed 1 month ago

joelreymont commented 4 months ago

I have a packet header header defined as follows.

How do I ensure that

  1. size is less than 1023,
  2. header_crc matches the checksum of the first 3 bytes of the packet, and
  3. packet_crc matches the checksum of the packet, excluding the 2 bytes of the checksum itself?

I will supply both checksum functions.

(defbinary duml-header (:byte-order :little-endian)
           (magic #x55 :type (magic :actual-type (unsigned-byte 8)
                                    :value #x55))
           (version 1 :type (unsigned-byte 6))
           ;; cannot be > 1023
           (size 0 :type (unsigned-byte 10))
           ;; crc of just the 3 bytes above
           (header-crc 0 :type (unsigned-byte 8))
           (sender-dev 0 :type (unsigned-byte 5))
           (sender-index 0 :type (unsigned-byte 3))
           (receiver-dev 0 :type (unsigned-byte 5))
           (receiver-index 0 :type (unsigned-byte 3))
           (seqnum 0 :type (unsigned-byte 16))
           (enc-type 0 :type (unsigned-byte 5))
           (ack-type 0 :type (unsigned-byte 2))
           (reply? 0 :type (unsigned-byte 1))
           (cmd-set 0 :type (unsigned-byte 8))
           (cmd-id 0 :type (unsigned-byte 8))
           (payload #() :type (simple-array (unsigned-byte 8) (- size 13)))
           ;; crc of the whole packet, including header and payload
           (packet-crc 0 :type (unsigned-byte 16))) 
j3pic commented 3 months ago

There's nothing in the library that handles validation. The closest that's implemented is the custom type specifier, which allows you to provide functions to read and write the field. If a lambda form is provided, it'll be a closure of a scope where all the fields of the struct that are available can be accessed as variables. In the writer, these variables are bound using cl:with-slots. These functions can do validation, but that comes at the cost of not being able to have code generated to do the actual reading and writing.

Your use case could be the impetus to implement a whole new feature: Each slot in a defbinary struct would have an optional :validator function that would have the option of returning T or NIL to indicte whether the value is valid, or signalling to give the user a customized error message.

Instead of a function, :validator could just be a form, which would be evaluated in a context where all the fields read so far would be bound to variables visible to the form.

Both usages could be supported simultaneously.

There would still be gaps for your use-case: When reading, Lisp-Binary only keeps the decoded versions of values read so far, but when checking a CRC, you'll want the raw bytes. When writing, I would want the writer for header-crc and packet-crc to calculate the CRC automatically. The best solution I can think of for this gap would be to wrap the stream in a Gray stream that caches the raw data being read or written, to make it available for the CRC calculation. Then, it's at least possible to check the CRC from a custom reader, and generate it in the custom writer function.