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
       ------------------------------------------------------------
 (HTM) [1]: Clojars
       
       
       Links
       ------------------------------------------------------------
 (BIN) master.zip
 (HTM) GitHub