Sebastian's Gopherspace (HTTP Gateway)

Introduction
------------------------------------------------------------
 
clojure-tlv is a type-length-value format implementation in
Clojure.
 
 
Installation
------------------------------------------------------------
 
This library can be installed from Clojars[1].
 
Leiningen/Boot
--------------
 
[zcfux/clojure-tlv "0.1.0-SNAPSHOT"]
 
 
Package format
------------------------------------------------------------
 
A package consists of the following fields:
 
 
Tag
---
 
The first byte of a package is called tag. The two leftmost
bits define the size of the length field.
 
 
  * 00: the package neither provides a length nor a payload field
  * 01: the length is stored in a single byte
  * 10: the length is stored in a word
  * 11: the length is stored in a double word
 
The package type is stored in the other six bits of the tag.
 
 
Length
------
 
This field indicates the payload length. It's stored in big
endian format.
 
 
Payload
-------
 
Variable-sized series of bytes.
 
 
Encoding
------------------------------------------------------------
 
A package can be created with the encode function. It
expects a package type and a sequence.
 
  (require '[clojure-tlv.core :as tlv])
 
  (tlv/encode 23 "hello world")
  => (87 11 104 101 108 108 111 32 119 111 114 108 100)
 
 
Decoding
------------------------------------------------------------
 
To decode packages clojure-tlv provides a pure functional
decoder. A callback function is applied to each found
package.
 
  (defn ->string
   [p]
   (String. (byte-array p)))
 
  (defn print-package
   [t p]
   (println (format
             "type: %d, payload: %s"
             t
             (->string p))))
 
 (-> (tlv/decoder print-package)
     (tlv/decode (tlv/encode 23 "hello world")))
 
 
Mapping types to keywords
-------------------------
 
Package types can be mapped to keywords automatically.
 
(defmulti process-package (fn [t p] t))
 
(defmethod process-package :foo
  [t p]
  (println "I'm a foo => " (->string p)))
 
(defmethod process-package :bar
  [t p]
  (println "I'm a bar => " (->string p)))
 
(-> (tlv/decoder process-package :type-map {23 :foo
                                            42 :bar})
    (tlv/decode (tlv/encode 23 "foo"))
    (tlv/decode (tlv/encode 42 "bar")))
 
 
Session state
-------------
 
It's also possible to implement a stateful decoder by
setting an initial session state. The state is passed to the
decoder's callback function as third argument and set to
the return value.
 
  (assert (zero? (-> (tlv/decoder (fn [t p s]
                                    (inc s))
                                  :session-state -1)
                     (tlv/decode (tlv/encode 5 "hello world"))
                     :session-state)))
 
 
Message size limit
------------------
 
A payload size limit can be set when defining a decoder. If
a message exceeds the specified limit the decoder becomes
invalid. Any new data will be ignored.
 
  (let [decoder (-> (tlv/decoder (fn [t p s]
                                   (inc s))
                                 :session-state -1
                                 :max-size 1)
                    (tlv/decode (tlv/encode 1 "a"))
                    (tlv/decode (tlv/encode 1 "bc")))]
    (assert (zero? (:session-state decoder)))
    (assert (tlv/failed? decoder))
    (assert (not (tlv/valid? decoder))))
 
 
Nested packages
---------------
 
clojure-tlv offers a function to unpack nested packages.
 
  (defn unpack-container
    [t p]
    (when-let [children (tlv/unpack p)]
      (println (clojure.string/join " "
                                    (map #(->string (second %))
                                         children)))))
 
  (-> (tlv/decoder unpack-container)
      (tlv/decode (tlv/encode 1 (concat (tlv/encode 2 "klaatu")
                                        (tlv/encode 2 "barada")
                                        (tlv/encode 2 "nikto")))))
 
 
Async support
-------------
 
clojure-tlv provides a simple core.async wrapper.
 
  (require '[clojure-tlv.async :as async]
           '[clojure.core.async :refer [>!! chan close!]])
 
  (def c (-> (tlv/decoder print-package)
             async/decoder->chan))
 
 (>!! c (concat (tlv/encode 1 "foo")
                 (tlv/encode 2 "bar")))
 
Alternatively you can use the convenient decode-async macro.
 
  (def c (chan))
 
  (async/decode-async c
                      ; decoder options
                      {:session-state 1
                       :max-size 256}
 
                      ; package received
                      ([t p s]
                       (println (format "package %d => %s"
                                        s
                                        (String. (byte-array p))))
                       (inc s))
 
                      ; error (channel is not closed automatically)
                      ([e]
                       (println "error => " e)
                       (close! c)))
 
  (>!! c (tlv/encode 1 "foobar"))
 
 
References
------------------------------------------------------------
[1]: Clojars
 
 
Links
------------------------------------------------------------
master.zip
GitHub