Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support for ref specs, fixed issue with assoc spec, added hex-dump, set version to 1.0.2 #11

Merged
merged 2 commits into from
Oct 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion project.clj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(defproject funcool/octet "1.0.1"
(defproject funcool/octet "1.0.2"
:description "A clojure(script) library for work with binary data."
:url "https://github.com/funcool/octet"
:license {:name "Public Domain"
Expand Down
3 changes: 3 additions & 0 deletions src/octet/core.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
[octet.spec.basic :as basic-spec]
[octet.spec.string :as string-spec]
[octet.spec.collections :as coll-spec]
[octet.spec.reference :as ref-spec]
[octet.buffer :as buffer]))

(util/defalias compose spec/compose)
Expand All @@ -41,6 +42,8 @@
(util/defalias string string-spec/string)
(util/defalias string* string-spec/string*)
(util/defalias vector* coll-spec/vector*)
(util/defalias ref-string* ref-spec/ref-string*)
(util/defalias ref-bytes* ref-spec/ref-bytes*)
(util/defalias int16 basic-spec/int16)
(util/defalias uint16 basic-spec/uint16)
(util/defalias int32 basic-spec/int32)
Expand Down
52 changes: 37 additions & 15 deletions src/octet/spec.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@

For more examples see the `spec` function docstring."
(:refer-clojure :exclude [type read float double long short byte bytes repeat])
(:require [octet.buffer :as buffer]))
(:require [octet.buffer :as buffer]
[octet.util :refer [assoc-ordered]]))

;; --- Protocols

Expand All @@ -48,6 +49,12 @@
"Abstraction for calculate size for dynamic specs."
(size* [_ data] "Calculate the size in bytes of the object having a data."))

(defprotocol ISpecWithRef
"Abstraction to support specs having references to other
specs within an AssociativeSpec or an IndexedSpec"
(read* [_ buff start data] "Read data from buffer, use data to calculate length etc")
(write* [_ buff start value types data] "Write data from buffer, use data to store length etc"))

;; --- Composed Spec Types

(deftype AssociativeSpec [data dict types]
Expand All @@ -74,20 +81,26 @@

ISpec
(read [_ buff pos]
(loop [index pos result {} pairs data]
(loop [index pos result (array-map) pairs data]
(if-let [[fieldname type] (first pairs)]
(let [[readedbytes readeddata] (read type buff index)]
(let [[readedbytes readeddata]
(if (satisfies? ISpecWithRef type)
(read* type buff index result)
(read type buff index))]
(recur (+ index readedbytes)
(assoc result fieldname readeddata)
(assoc-ordered result fieldname readeddata)
(rest pairs)))
[(- index pos) result])))
[(- index pos) result])))

(write [_ buff pos data']
(let [written (reduce (fn [index [fieldname type]]
(let [value (get data' fieldname nil)
written (write type buff index value)]
(+ index written)))
pos data)]
(let [written
(reduce (fn [index [fieldname type]]
(let [value (get data' fieldname nil)
written (if (satisfies? ISpecWithRef type)
(write* type buff index value dict data')
(write type buff index value))]
(+ index written)))
pos data)]
(- written pos))))

(deftype IndexedSpec [types]
Expand Down Expand Up @@ -116,7 +129,10 @@
(read [_ buff pos]
(loop [index pos result [] types types]
(if-let [type (first types)]
(let [[readedbytes readeddata] (read type buff index)]
(let [[readedbytes readeddata]
(if (satisfies? ISpecWithRef type)
(read* type buff index result)
(read type buff index))]
(recur (+ index readedbytes)
(conj result readeddata)
(rest types)))
Expand All @@ -126,7 +142,9 @@
(let [indexedtypes (map-indexed vector types)
written (reduce (fn [pos [index type]]
(let [value (nth data' index nil)
written (write type buff pos value)]
written (if (satisfies? ISpecWithRef type)
(write* type buff pos value types data')
(write type buff pos value))]
(+ pos written)))
pos indexedtypes)]
(- written pos))))
Expand All @@ -138,10 +156,14 @@

;; --- Spec Constructors

(defn- spec? [s]
(or (satisfies? ISpecWithRef s)
(satisfies? ISpec s)))

(defn- associative-spec
[& params]
(let [data (mapv vec (partition 2 params))
dict (into {} data)
dict (apply array-map params)
types (map second data)]
(AssociativeSpec. data dict types)))

Expand Down Expand Up @@ -179,12 +201,12 @@
[& params]
(let [numparams (count params)]
(cond
(every? #(satisfies? ISpec %) params)
(every? spec? params)
(apply indexed-spec params)

(and (even? numparams)
(keyword? (first params))
(satisfies? ISpec (second params)))
(spec? (second params)))
(apply associative-spec params)

:else
Expand Down
143 changes: 143 additions & 0 deletions src/octet/spec/reference.cljc
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
;; All rights reserved.
;;
;; Redistribution and use in source and binary forms, with or without
;; modification, are permitted provided that the following conditions are met:
;;
;; * Redistributions of source code must retain the above copyright notice, this
;; list of conditions and the following disclaimer.
;;
;; * Redistributions in binary form must reproduce the above copyright notice,
;; this list of conditions and the following disclaimer in the documentation
;; and/or other materials provided with the distribution.
;;
;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
;; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
;; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
;; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
;; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
;; CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
;; OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

(ns octet.spec.reference
(:require [octet.buffer :as buffer]
[octet.spec :as spec]
[octet.spec.string :as string-spec]))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Spec types for arbitrary length byte arrays/strings with a length reference
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defn- ref-size [type data]
(if (satisfies? spec/ISpecSize type)
(.size type)
(.size* type data)))

(defn- coerce-types [types]
"to make it terser to handle both maps (assoc spec) and seq/vector
(indexed spec), we coerce the seq/vector types to maps where the
keys are indexes"
(cond (map? types) types
(or (seq? types)
(vector? types)) (apply array-map (interleave (range) types))
:else (throw (ex-info "invalid type structure, not map, seq or vector"
{:type-structure types}))))

(defn- ref-len-offset [ref-kw-or-index types data]
"for ref-xxxx* specs, calculate the byte offset of the
spec containing the length, i.e. (spec :a (int16) :b (int16) c: (ref-string* :b))
would cause this method to be called with :b (since the ref-string has a :b
reference as its length) and should then return 2 as the second int16 is at byte offset 2"
(reduce-kv
(fn [acc kw-or-index type]
(if (= ref-kw-or-index kw-or-index)
(reduced acc)
(+ acc (ref-size type (get data kw-or-index)))))
0
types))

(defn- ref-write* [ref-kw-or-index buff pos value types data]
"write a ref spec, will also write the length to the length spec"
(let [input (if (string? value) (string-spec/string->bytes value) value)
length (count input)
types (coerce-types types)
len-offset (ref-len-offset ref-kw-or-index types data)
len-type (get types ref-kw-or-index)]
(.write len-type buff len-offset length)
(buffer/write-bytes buff pos length input)
(+ length)))

(defn- ref-read* [ref-kw-or-index buff pos parent]
"read ref spec, will read the length from the length spec"
(let [datasize (cond (map? parent) (ref-kw-or-index parent)
(or (seq? parent) (vector? parent)) (get parent ref-kw-or-index)
:else (throw (ex-info
(str "bad ref-string*/ref-bytes* length reference - " ref-kw-or-index)
{:length-kw ref-kw-or-index
:data-read parent})))
data (buffer/read-bytes buff pos datasize)]
[datasize data]))

(defn ref-bytes*
[ref-kw-or-index]
"create a dynamic length byte array where the length of the byte array is stored in another
spec within the containing indexed spec or associative spec. Example usages:
(spec (int16) (int16) (ref-bytes* 1))
(spec :a (int16) :b (int32) (ref-bytes* :b))
where the first example would store the length of the byte array in the second int16 and
the second example would store the length of the byte array in the int32 at key :b."
(reify
#?@(:clj
[clojure.lang.IFn
(invoke [s] s)]
:cljs
[cljs.core/IFn
(-invoke [s] s)])

spec/ISpecDynamicSize
(size* [_ data]
(count data))

spec/ISpecWithRef
(read* [_ buff pos parent]
(ref-read* ref-kw-or-index buff pos parent))

(write* [_ buff pos value types data]
(ref-write* ref-kw-or-index buff pos value types data))))

(defn ref-string*
"create a dynamic length string where the length of the string is stored in another
spec within the containing indexed spec or associative spec. Example usages:
(spec (int16) (int16) (ref-string* 1))
(spec :a (int16) :b (int32) (ref-string* :b))
where the first example would store the length of the string in the second int16 and
the second example would store the length of the string in the int32 at key :b."
[ref-kw-or-index]
(reify
#?@(:clj
[clojure.lang.IFn
(invoke [s] s)]
:cljs
[cljs.core/IFn
(-invoke [s] s)])

spec/ISpecDynamicSize
(size* [_ data]
(let [data (string-spec/string->bytes data)]
(count data)))

spec/ISpecWithRef
(read* [_ buff pos parent]
(let [[datasize bytes] (ref-read* ref-kw-or-index buff pos parent)]
[datasize (string-spec/bytes->string bytes datasize)]))

(write* [_ buff pos value types data]
(ref-write* ref-kw-or-index buff pos value types data))))

(defn ref-vector*
[ref-kw-or-index]
;; TODO: implement this
)
Loading