Previous: Bindat Functions, Up: Byte Packing

37.20.3 Examples of Byte Unpacking and Packing

Here is a complete example of byte unpacking and packing:

     (require 'bindat)
     
     (defvar fcookie-index-spec
       '((:version  u32)
         (:count    u32)
         (:longest  u32)
         (:shortest u32)
         (:flags    u32)
         (:delim    u8)
         (:ignored  fill 3)
         (:offset   repeat (:count) (:foo u32)))
       "Description of a fortune cookie index file's contents.")
     
     (defun fcookie (cookies &optional index)
       "Display a random fortune cookie from file COOKIES.
     Optional second arg INDEX specifies the associated index
     filename, by default \"COOKIES.dat\".  Display cookie text
     in buffer \"*Fortune Cookie: BASENAME*\", where BASENAME
     is COOKIES without the directory part."
       (interactive "fCookies file: ")
       (let* ((info (with-temp-buffer
                      (insert-file-contents-literally
                       (or index (concat cookies ".dat")))
                      (bindat-unpack fcookie-index-spec
                                     (buffer-string))))
              (sel (random (bindat-get-field info :count)))
              (beg (cdar (bindat-get-field info :offset sel)))
              (end (or (cdar (bindat-get-field info
                                               :offset (1+ sel)))
                       (nth 7 (file-attributes cookies)))))
         (switch-to-buffer
          (get-buffer-create
           (format "*Fortune Cookie: %s*"
                   (file-name-nondirectory cookies))))
         (erase-buffer)
         (insert-file-contents-literally
          cookies nil beg (- end 3))))
     
     (defun fcookie-create-index (cookies &optional index delim)
       "Scan file COOKIES, and write out its index file.
     Optional arg INDEX specifies the index filename, which by
     default is \"COOKIES.dat\".  Optional arg DELIM specifies the
     unibyte character that, when found on a line of its own in
     COOKIES, indicates the border between entries."
       (interactive "fCookies file: ")
       (setq delim (or delim ?%))
       (let ((delim-line (format "\n%c\n" delim))
             (count 0)
             (max 0)
             min p q len offsets)
         (unless (= 3 (string-bytes delim-line))
           (error "Delimiter cannot be represented in one byte"))
         (with-temp-buffer
           (insert-file-contents-literally cookies)
           (while (and (setq p (point))
                       (search-forward delim-line (point-max) t)
                       (setq len (- (point) 3 p)))
             (setq count (1+ count)
                   max (max max len)
                   min (min (or min max) len)
                   offsets (cons (1- p) offsets))))
         (with-temp-buffer
           (set-buffer-multibyte nil)
           (insert
            (bindat-pack
             fcookie-index-spec
             `((:version . 2)
               (:count . ,count)
               (:longest . ,max)
               (:shortest . ,min)
               (:flags . 0)
               (:delim . ,delim)
               (:offset . ,(mapcar (lambda (o)
                                     (list (cons :foo o)))
                                   (nreverse offsets))))))
           (let ((coding-system-for-write 'raw-text-unix))
             (write-file (or index (concat cookies ".dat")))))))

The following is an example of defining and unpacking a complex structure. Consider the following C structures:

     struct header {
         unsigned long    dest_ip;
         unsigned long    src_ip;
         unsigned short   dest_port;
         unsigned short   src_port;
     };
     
     struct data {
         unsigned char    type;
         unsigned char    opcode;
         unsigned short   length;  /* in network byte order  */
         unsigned char    id[8];   /* null-terminated string  */
         unsigned char    data[/* (length + 3) & ~3 */];
     };
     
     struct packet {
         struct header    header;
         unsigned long    counters[2];  /* in little endian order  */
         unsigned char    items;
         unsigned char    filler[3];
         struct data      item[/* items */];
     
     };

The corresponding data layout specification is:

     (setq header-spec
           '((dest-ip   ip)
             (src-ip    ip)
             (dest-port u16)
             (src-port  u16)))
     
     (setq data-spec
           '((type      u8)
             (opcode    u8)
             (length    u16)  ; network byte order
             (id        strz 8)
             (data      vec (length))
             (align     4)))
     
     (setq packet-spec
           '((header    struct header-spec)
             (counters  vec 2 u32r)   ; little endian order
             (items     u8)
             (fill      3)
             (item      repeat (items)
                        (struct data-spec))))

A binary data representation is:

     (setq binary-data
           [ 192 168 1 100 192 168 1 101 01 28 21 32
             160 134 1 0 5 1 0 0 2 0 0 0
             2 3 0 5 ?A ?B ?C ?D ?E ?F 0 0 1 2 3 4 5 0 0 0
             1 4 0 7 ?B ?C ?D ?E ?F ?G 0 0 6 7 8 9 10 11 12 0 ])

The corresponding decoded structure is:

     (setq decoded (bindat-unpack packet-spec binary-data))
          ⇒
     ((header
       (dest-ip   . [192 168 1 100])
       (src-ip    . [192 168 1 101])
       (dest-port . 284)
       (src-port  . 5408))
      (counters . [100000 261])
      (items . 2)
      (item ((data . [1 2 3 4 5])
             (id . "ABCDEF")
             (length . 5)
             (opcode . 3)
             (type . 2))
            ((data . [6 7 8 9 10 11 12])
             (id . "BCDEFG")
             (length . 7)
             (opcode . 4)
             (type . 1))))

An example of fetching data from this structure:

     (bindat-get-field decoded 'item 1 'id)
          ⇒ "BCDEFG"