├── .gitignore ├── project.clj ├── README.md ├── test └── bytebuffer │ └── buff_test.clj └── src └── bytebuffer └── buff.clj /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | *jar 3 | lib 4 | classes 5 | autodoc/** 6 | .lein-failures 7 | .lein-deps-sum 8 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject bytebuffer "0.2.0" 2 | :description "Library for packing and unpacking binary data. Simplifies using java.util.ByteBuffer objects." 3 | :dependencies [[org.clojure/clojure "1.3.0"]] 4 | ;; :dev-dependencies [[autodoc "0.7.1"]] 5 | :autodoc {:name "Bytebuffer" :page-title "Bytebuffer API Documentation" 6 | :web-src-dir "http://github.com/geoffsalmon/bytebuffer/blob/"}) 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bytebuffer 2 | 3 | Library for packing and unpacking binary data. Simplifies working with 4 | [java.nio.ByteBuffer](http://java.sun.com/j2se/1.5.0/docs/api/java/nio/ByteBuffer.html) 5 | objects. 6 | 7 | Handles signed and unsigned values pleasantly. Usually reading or 8 | writing unsigned fields with ByteBuffers is a pain because Java 9 | doesn't have unsigned primitives. The unsigned take-* functions here 10 | actually return a type that is one step larger than the requested 11 | type, ex. take-ushort returns an int, and take-uint returns a 12 | bigint. 13 | 14 | See the [API Docs](http://geoffsalmon.github.com/bytebuffer/index.html) 15 | 16 | If you're serializing or deserializing data, you should also checkout 17 | [gloss](https://github.com/ztellman/gloss). 18 | 19 | ## Usage 20 | 21 | Use put-* and take-* functions for each data type add to and remove 22 | from a bytebuffer. 23 | 24 | (def buff (byte-buffer 100)) 25 | (put-int buff 84) 26 | (put-byte buff -44) 27 | (put-byte buff -44) 28 | (.flip buff) 29 | (take-int buff) => 84 30 | (take-byte buff) => -44 31 | (take-ubyte buff) => 212 ; -44 is interpreted as 212 when read as unsigned byte 32 | 33 | Use with-buffer to bind a default buffer and avoid passing it to every 34 | take-* and put-* function. 35 | 36 | (let [buff (byte-buffer 100)] 37 | (with-buffer buff 38 | (put-int 10) 39 | (put-int 6) 40 | (.flip buff) 41 | [(take-int) (take-int)] 42 | ) 43 | ) => [10 6] 44 | 45 | A more compact way to add or remove data is the pack and unpack 46 | functions, which are inspired by Python's struct module. They use 47 | simple format strings, similar to printf's, to define how fields are 48 | arranged in the buffer. 49 | 50 | (pack buff "isbb" 123 43 23 3) ; puts an int, a short and two bytes into buff 51 | (.flip buff) 52 | (unpack buff "isbb") => (123 43 24 3) 53 | 54 | Use the functions pack-bits and unpack-bits to work with bit fields 55 | within numbers. 56 | 57 | (pack-bits 2 3 2 true 3 false 1 1) => 2r11010001 58 | 59 | (unpack-bits 2r101011001 4 \b 1 1 2) => (10 true 1 0 1) 60 | 61 | When parsing binary file or packet structures it's often useful to 62 | pull off a larger piece of data as a separate buffer using slice-off. 63 | 64 | (defn parse-header [buff] ...) 65 | (defn parse-body [buff] ...) 66 | 67 | (let [hdr (parse-header (slice-off buff 18)) ; slice off 18 byte header 68 | body (parse-body (slice-off buff (:len hdr))] ; parse body 69 | ... 70 | ) 71 | 72 | See the 73 | [tests](http://github.com/geoffsalmon/bytebuffer/blob/master/test/bytebuffer/buff_test.clj) 74 | for more examples. 75 | 76 | ## Installation 77 | 78 | Either grab the source or use the jar from clojars. Version 0.2.0 79 | works with Clojure 1.3. 80 | 81 | ### Leiningen 82 | 83 | Add `[bytebuffer "0.2.0"]` to :dependencies in project.clj. 84 | 85 | ### Maven 86 | 87 | Add this dependency 88 | 89 | 90 | bytebuffer 91 | bytebuffer 92 | 0.2.0 93 | 94 | 95 | and ensure you have the following maven repository added. 96 | 97 | 98 | clojars.org 99 | http://clojars.org/repo 100 | 101 | 102 | ## License 103 | 104 | Copyright (c) Geoff Salmon 105 | 106 | Licensed under [EPL 1.0](http://www.eclipse.org/legal/epl-v10.html) 107 | -------------------------------------------------------------------------------- /test/bytebuffer/buff_test.clj: -------------------------------------------------------------------------------- 1 | (ns bytebuffer.buff-test 2 | (:use [bytebuffer.buff] :reload-all) 3 | (:use [clojure.test]) 4 | (:import (java.nio ByteBuffer ByteOrder))) 5 | 6 | ; choose values that have a different value for each byte 7 | (def b (byte 0x1)) 8 | (def s 0x302) 9 | (def i 0x7060504) 10 | (def l 0x0f0e0d0c0b0a0908) 11 | 12 | ;; compute powers of 2 13 | (def pows 14 | (conj 15 | (vec (reduce (fn [list _] (conj list (* 2 (peek list)))) [1] (range 62))) 16 | ;; 2^63 17 | 0x8000000000000000N)) 18 | 19 | ;; computer powers of 2 minus 1 20 | (def pows-1 21 | {8 0xFF 22 | 16 0xFFFF 23 | 32 0xFFFFFFFF 24 | 64 0xFFFFFFFFFFFFFFFF}) 25 | 26 | (defn pow2 [e] (nth pows e)) 27 | 28 | (deftest test-packing 29 | "Check that buffers filled with pack are identical to those 30 | filled callin the Java put* methods" 31 | (is (= 32 | (doto (ByteBuffer/allocate 100) 33 | (.put b) 34 | (.putShort s) 35 | (.putInt i) 36 | (.putLong l) 37 | (.flip) 38 | ) 39 | (.flip (pack (byte-buffer 100) "bsil" b s i l)))) 40 | ) 41 | 42 | (deftest test-packing-error 43 | (is (thrown? Exception 44 | (pack (byte-buffer 100) "x" 1))) 45 | 46 | (is (thrown? Exception 47 | (pack (byte-buffer 100) "bb" 1))) 48 | 49 | (is (thrown? Exception 50 | (pack (byte-buffer 100) "b" 1 1))) 51 | ) 52 | 53 | (deftest test-unpacking-error 54 | (let [buff (.flip (pack (byte-buffer 100) "i" 1))] 55 | (is (thrown? Exception 56 | (unpack buff "x"))) 57 | 58 | ) 59 | ) 60 | 61 | (deftest test-unpacking 62 | "Check that unpack is the inverse of pack" 63 | (let [vals [b s i l] 64 | fmt "bsil" 65 | buff (.flip (apply pack (byte-buffer 100) fmt vals))] 66 | (is (= 67 | vals 68 | (unpack buff fmt) 69 | ))) 70 | ) 71 | 72 | (deftest test-signed-unsigned 73 | (let [max-unsigned (sort (vals pows-1)) 74 | mid-unsigned (map #(pow2 %) [7 15 31 63]) 75 | min-signed (map - mid-unsigned) 76 | 77 | buff 78 | (.flip 79 | (apply pack (byte-buffer 100) 80 | "bsilbsilbsilbsil" 81 | 82 | (concat 83 | (repeat 4 -1) 84 | max-unsigned 85 | mid-unsigned 86 | min-signed 87 | ) 88 | ))] 89 | 90 | ; unpack as signed variables 91 | (is (= (repeat 8 -1) 92 | (unpack buff "bsilbsil"))) 93 | 94 | (is (= (apply concat (repeat 2 min-signed)) 95 | (unpack buff "bsilbsil"))) 96 | 97 | (.position buff 0) ; reread buffer from start 98 | 99 | ; unpack as unsigned variables 100 | (is (= (apply concat (repeat 2 max-unsigned)) 101 | (unpack buff "BSILBSIL"))) 102 | 103 | (is (= (apply concat (repeat 2 mid-unsigned)) 104 | (unpack buff "BSILBSIL"))) 105 | ) 106 | ) 107 | 108 | (def b15 (pow2 15)) 109 | (def b16 (pow2 16)) 110 | (def b31 (pow2 31)) 111 | (def b32 (pow2 32)) 112 | (def b63 (pow2 63)) 113 | 114 | (defn- pack-flip [fmt & vars] 115 | (.flip (apply pack (byte-buffer 100) fmt vars)) 116 | ) 117 | 118 | (deftest test-take-bytes 119 | (let [buff (pack-flip "bbbbbb" 0 0 -128 -128 -1 -1)] 120 | (is (= 0 (take-byte buff))) 121 | (is (= 0 (take-ubyte buff))) 122 | 123 | (is (= -128 (take-byte buff))) 124 | (is (= 128 (take-ubyte buff))) 125 | 126 | (is (= -1 (take-byte buff))) 127 | (is (= 255 (take-ubyte buff))))) 128 | 129 | (deftest test-take-short 130 | (let [buff (pack-flip "ssssss" 0 0 (- b15) (- b15) -1 -1)] 131 | 132 | (is (= 0 (take-short buff))) 133 | (is (= 0 (take-ushort buff))) 134 | 135 | (is (= (- b15) (take-short buff))) 136 | (is (= b15 (take-ushort buff))) 137 | 138 | (is (= -1 (take-short buff))) 139 | (is (= (- b16 1) (take-ushort buff))))) 140 | 141 | (deftest test-take-int 142 | (let [buff (pack-flip "iiiiii" 0 0 (- b31) (- b31) -1 -1)] 143 | 144 | (is (= 0 (take-int buff))) 145 | (is (= 0 (take-uint buff))) 146 | 147 | (is (= (- b31) (take-int buff))) 148 | (is (= b31 (take-uint buff))) 149 | 150 | (is (= -1 (take-int buff))) 151 | (is (= (- b32 1) (take-uint buff))))) 152 | 153 | (deftest test-take-long 154 | (let [buff (pack-flip "llllll" 0 0 (- b63) (- b63) -1 -1)] 155 | 156 | (is (= 0 (take-long buff))) 157 | (is (= 0 (take-ulong buff))) 158 | 159 | (is (= (- b63) (take-long buff))) 160 | (is (= b63 (take-ulong buff))) 161 | 162 | (is (= -1 (take-long buff))) 163 | (is (= (pows-1 64) (take-ulong buff))))) 164 | 165 | 166 | (deftest test-bit-pack 167 | (is (= 0x120428 (pack-bits 4 1 4 2 4 0 4 4 8 0x28))) 168 | 169 | 170 | (is (= 2r11010001 (pack-bits 2 3 2 true 3 false 1 true)) "use booleans for values 0 and 1") 171 | 172 | (is (thrown? Exception 173 | (pack-bits 4 1 4 5 4)) "field without value") 174 | 175 | (is (thrown? Exception 176 | (pack-bits 4 1 4 -5 4 2)) "negative values") 177 | 178 | (is (thrown? Exception 179 | (pack-bits 4 1 0 0 4 2)) "zero bit lengths") 180 | 181 | (is (thrown? Exception 182 | (pack-bits 4 1 -10 5 4 2)) "negative bit lengths") 183 | 184 | (is (thrown? Exception 185 | (pack-bits 4 1 4 16 4 2)) "insufficient bit lengths") 186 | ) 187 | 188 | (deftest test-bit-unpack 189 | (is (= [0 0 0x12 0x34 0x5] (unpack-bits 0x12345 3 9 8 8 4))) 190 | 191 | (is (= [0x12 0x5] (unpack-bits 0x12345 8 -8 4)) "Skip bits") 192 | 193 | (is (= [2r1010 true false 2r01] (unpack-bits 2r10101001 4 \b \b 2)) "bits as booleans") 194 | 195 | (is (= [0x12 0x34 0x5] (unpack-bits 0x12345 0 0 8 8 0 4)) "Weird 0 bit length. Should this be an error instead?") 196 | ) 197 | 198 | (deftest slice 199 | (let [buff (pack-flip "bbbbbb" 10 11 12 13 14 15)] 200 | (is (thrown? IndexOutOfBoundsException (slice-off buff 20))) 201 | (let [b1 (slice-off buff 4)] 202 | (is (= [10 11 12 13] (unpack b1 "bbbb"))) 203 | (is (zero? (.remaining b1))) 204 | 205 | (is (thrown? IndexOutOfBoundsException (slice-off buff 4))) 206 | (is (= [14 15] (unpack buff "bb"))) 207 | ) 208 | )) 209 | 210 | (deftest test-fmt-size 211 | (is (= 0 (fmt-size ""))) 212 | (let [fmts "bBsSiIlL" 213 | rand-fmt (fn [n] (apply str (for [i (range n)] (rand-nth fmts)))) 214 | buf (byte-buffer 100) 215 | ;; compare fmt-size result against resulting position after 216 | ;; packing zeroes into a buffer 217 | test (fn [fmt] 218 | (.clear buf) 219 | (apply pack buf fmt (repeat (.length fmt) 0)) 220 | (is (= (.position buf) (fmt-size fmt))))] 221 | ;; test all single letter fmt strings 222 | (doseq [fmt fmts] 223 | (test (str fmt))) 224 | ;; test random fmt strings of length >= 2 225 | (doseq [i (range 20)] 226 | (test (rand-fmt (+ 2 (rand-int 10))))))) 227 | -------------------------------------------------------------------------------- /src/bytebuffer/buff.clj: -------------------------------------------------------------------------------- 1 | (ns 2 | #^{ 3 | :doc "Library for packing and unpacking binary data. Simplifies working 4 | with java.nio.ByteBuffer objects. 5 | 6 | Notable features: 7 | 8 | 1. Handles signed and unsigned values pleasantly. Usually reading or 9 | writing unsigned fields with ByteBuffers is a pain because Java 10 | doesn't have unsigned primitives. The take-* functions return a long 11 | which is big enough to handle the entire unsigned range of most 12 | types. To handle large unsigned longs, the take-ulong function can 13 | return either a long or a clojure.lang.BigInt. Similarly, although 14 | there are not separate signed and unsigned version, the put-* 15 | functions will accept any number type and truncate it so both positive 16 | and negative numbers can be stored. TODO: Should add an overflow check 17 | to avoid overflows in put-* functions? 18 | 19 | 2. Provides pack and unpack functions inspired by Python's struct 20 | module. Use simple format strings, similar to printf's, to define how 21 | fields are layed out in the buffer. 22 | 23 | Usage: 24 | (pack buff \"isbb\" 123 43 23 3) ; puts an int, a short and two bytes into buff 25 | (.flip buff) ; assuming nothing else was written to the buffer 26 | (unpack buff \"isbb\") => (123 43 24 3) 27 | 28 | 3. Provides pack-bits and unpack-bits functions for working with bit 29 | fields within numbers. These are useful for pulling apart flag fields. 30 | " 31 | :see-also [["http://java.sun.com/j2se/1.5.0/docs/api/java/nio/ByteBuffer.html" "java.nio.ByteBuffer Documentation"]] 32 | } 33 | 34 | bytebuffer.buff 35 | (:import (java.nio ByteBuffer ByteOrder)) 36 | ) 37 | 38 | (defn byte-buffer 39 | "Creates a ByteBuffer of capacity bytes" 40 | [capacity] 41 | (ByteBuffer/allocate capacity)) 42 | 43 | #_(def ^{:tag :dynamic 44 | :private true 45 | :doc "The current buffer. Use with-buffer to bind this."} 46 | *byte-buffer* nil) 47 | 48 | (def ^:dynamic *byte-buffer* nil) 49 | 50 | (defmacro with-buffer 51 | "Sets the buffer currently being used by the put-* and take-* 52 | functions which do not take buffers." 53 | [buffer & body] 54 | `(binding [*byte-buffer* ~buffer] 55 | ~@body)) 56 | 57 | (defn put-byte 58 | "Puts a byte into the buffer" 59 | ([val] 60 | (put-byte *byte-buffer* val)) 61 | ([^ByteBuffer buff val] 62 | (.put buff (.byteValue val))) 63 | ) 64 | 65 | (defn put-short 66 | "Puts a short (2 bytes) into the buffer" 67 | ([val] 68 | (put-short *byte-buffer* val)) 69 | ([^ByteBuffer buff val] 70 | (.putShort buff (.shortValue val))) 71 | ) 72 | 73 | (defn put-int 74 | "Puts an int (4 bytes) into the buffer" 75 | ([val] 76 | (put-int *byte-buffer* val)) 77 | ([^ByteBuffer buff val] 78 | (.putInt buff (.intValue val))) 79 | ) 80 | 81 | (defn put-long 82 | "Puts a long (8 bytes) into the buffer" 83 | ([val] 84 | (put-long *byte-buffer* val)) 85 | ([^ByteBuffer buff val] 86 | (.putLong buff (.longValue val))) 87 | ) 88 | 89 | (defn take-byte 90 | "Takes a signed byte from the buffer" 91 | (^long [] 92 | (take-byte *byte-buffer*)) 93 | (^long [^ByteBuffer buff] 94 | (long (.get buff))) 95 | ) 96 | 97 | (defn take-ubyte 98 | "Takes an unsigned signed byte from the buffer" 99 | (^long [] 100 | (take-ubyte *byte-buffer*)) 101 | (^long [^ByteBuffer buff] 102 | (bit-and 0xFF (long (.get buff)))) 103 | ) 104 | 105 | (defn take-short 106 | "Takes a signed short (2 bytes) from the buffer" 107 | (^long [] 108 | (take-short *byte-buffer*)) 109 | (^long [^ByteBuffer buff] 110 | (long (.getShort buff))) 111 | ) 112 | 113 | (defn take-ushort 114 | "Takes a unsigned short (2 bytes) from the buffer" 115 | (^long [] 116 | (take-ushort *byte-buffer*)) 117 | (^long [^ByteBuffer buff] 118 | (bit-and 0xFFFF (long (.getShort buff)))) 119 | ) 120 | 121 | (defn take-int 122 | "Takes a signed int (4 bytes) from the buffer" 123 | (^long [] 124 | (take-int *byte-buffer*)) 125 | (^long [^ByteBuffer buff] 126 | (.getInt buff)) 127 | ) 128 | 129 | (defn take-uint 130 | "Takes a unsigned int (4 bytes) from the buffer" 131 | (^long [] 132 | (take-uint *byte-buffer*)) 133 | (^long [^ByteBuffer buff] 134 | (bit-and 0xFFFFFFFF (long (.getInt buff)))) 135 | ) 136 | 137 | (defn take-long 138 | "Takes a signed long (8 bytes) from the buffer" 139 | (^long [] 140 | (take-long *byte-buffer*)) 141 | (^long [^ByteBuffer buff] 142 | (.getLong buff)) 143 | ) 144 | 145 | (defn take-ulong 146 | "Takes a unsigned long (8 bytes) from the buffer" 147 | ([] 148 | (take-ulong *byte-buffer*)) 149 | ([^ByteBuffer buff] 150 | (let [l (.getLong buff)] 151 | (if (>= l 0) 152 | l 153 | ;; add 2^64 to treat the negative 64bit 2's complement 154 | ;; num as unsigned. 155 | (+ 18446744073709551616N (bigint l))))) 156 | ) 157 | 158 | 159 | (defn ^ByteBuffer slice-off [^ByteBuffer buff len] 160 | "Create a new bytebuffer by slicing off the first len bytes. Also 161 | consumes the bytes in the given buffer." 162 | (if (> len (.remaining buff)) 163 | (throw (IndexOutOfBoundsException. (str "Cannot slice-off " len " bytes of remaining " (.remaining buff) " bytes.") )) 164 | (let [rdbuf (-> buff (.slice) (.limit len))] 165 | (.position buff (+ (.position buff) len)) ; advance the actual buffer 166 | rdbuf 167 | )) 168 | ) 169 | 170 | (defn fmt-size [fmt] 171 | (reduce (fn [acc f] 172 | (+ acc 173 | (condp = f 174 | \b 1 175 | \B 1 176 | \s 2 177 | \S 2 178 | \i 4 179 | \I 4 180 | \l 8 181 | \L 8 182 | (throw (IllegalArgumentException. (str "Unknown format symbol \"" fmt \"))) 183 | ))) 0 fmt)) 184 | 185 | (defn- pack-one [buff fmt val] 186 | (condp = fmt 187 | \b (put-byte buff val) 188 | \B (put-byte buff val) 189 | \s (put-short buff val) 190 | \S (put-short buff val) 191 | \i (put-int buff val) 192 | \I (put-int buff val) 193 | \l (put-long buff val) 194 | \L (put-long buff val) 195 | (throw (IllegalArgumentException. (str "Unknown format symbol \"" fmt \"))) 196 | )) 197 | 198 | (defn pack 199 | "Puts one or more numbers for vals into buff using field sizes 200 | determined by the characters in the fmt sequence. Valid characters are 201 | b - byte 202 | s - short 203 | i - int 204 | l - long 205 | 206 | The number of characters in fmt must match the number of numbers in vals. 207 | 208 | Usage: (pack buff \"isbb\" 123 43 23 3) ; puts an int, a short and two bytes into buff 209 | 210 | Returns buff." 211 | [buff fmt & vals] 212 | (when-not (= (count fmt) (count vals)) 213 | (throw (IllegalArgumentException. "pack error. Number of format symbols must match number of values."))) 214 | 215 | (doseq [[f val] (partition 2 (interleave fmt vals))] 216 | (pack-one buff f val)) 217 | buff 218 | ) 219 | 220 | (defn- unpack-one [buff fmt] 221 | (condp = fmt 222 | \b (take-byte buff) 223 | \B (take-ubyte buff) 224 | \s (take-short buff) 225 | \S (take-ushort buff) 226 | \i (take-int buff) 227 | \I (take-uint buff) 228 | \l (take-long buff) 229 | \L (take-ulong buff) 230 | (throw (IllegalArgumentException. (str "Unknown format symbol \"" fmt \"))) 231 | )) 232 | 233 | (defn unpack 234 | "Returns a sequence of one or more numbers taken from buff. The 235 | number and type of numbers taken is determined by fmt which is a 236 | sequence of characters. Valid characters in fmt: 237 | 238 | b - byte 239 | B - unsigned byte 240 | s - short 241 | S - unsigned short 242 | i - int 243 | I - unsigned int 244 | l - long 245 | L - unsigned long" 246 | [buff fmt] 247 | (doall (map (partial unpack-one buff) fmt)) 248 | ) 249 | 250 | (defn- bit-val [x] 251 | (if (instance? Boolean x) 252 | (if x 1 0) 253 | x 254 | ) 255 | ) 256 | 257 | (defn pack-bits 258 | "Packs multiple numbers into a single number using explicit bit lengths. 259 | 260 | fields => bit-length value 261 | 262 | The value can also be a boolean. true is stored as 1, false as 0" 263 | ([] 0) 264 | ([& fields] 265 | (when-not (zero? (mod (count fields) 2)) 266 | (throw (IllegalArgumentException. (str "pack-bits Last field does not have a value."))) 267 | ) 268 | 269 | (reduce (fn [acc [num-bits val-in]] 270 | (let [val (bit-val val-in)] 271 | (when-not (pos? num-bits) 272 | (throw (IllegalArgumentException. 273 | (str "pack-bits: Invalid bit length " num-bits ". Must be positive.")))) 274 | 275 | (when (neg? val) 276 | (throw (IllegalArgumentException. 277 | (str "pack-bits: Invalid value " val ". Must be non-negative.")))) 278 | 279 | ; ensure specified bit length was big enough 280 | (when-not (= val 281 | (bit-and val (dec (bit-shift-left 1 num-bits))) 282 | 283 | ) 284 | (throw (IllegalArgumentException. 285 | (str "pack-bits: Invalid value " val ". Must fit in " num-bits " bits as specified."))) 286 | ) 287 | 288 | (-> acc 289 | (bit-shift-left num-bits) 290 | (bit-or val) 291 | )) 292 | ) 293 | 0 (partition 2 fields)) 294 | ) 295 | ) 296 | 297 | (defn unpack-bits 298 | "Pulls apart a number into a list of fields of various bit lengths. 299 | Pass a non-positive bit length to skip that many bits without adding a 300 | corresponding value to the result list. 301 | 302 | Passing a field length of 1 will add either a 0 or a 1 to the 303 | resulting sequence. To get a boolean value instead, pass \\b." 304 | [x & bit-lengths] 305 | (loop [val x 306 | results '() 307 | ; iterate bit lengths in reverse to continually pull 308 | ; values from the low order bits 309 | rbit-lens (reverse bit-lengths)] 310 | 311 | (if (= '() rbit-lens) 312 | results 313 | 314 | (let [bits (first rbit-lens)] 315 | (cond 316 | (= \b bits) ; grab single bit as boolean 317 | (recur (bit-shift-right val 1) 318 | (cons (= 1 (bit-and val 1)) results) 319 | (rest rbit-lens) 320 | ) 321 | 322 | (pos? bits) 323 | (recur (bit-shift-right val bits) 324 | (cons 325 | (bit-and val (dec (bit-shift-left 1 bits))) 326 | results) 327 | (rest rbit-lens) 328 | ) 329 | :else ; skip bits 330 | (recur (bit-shift-right val (- bits)) 331 | results 332 | (rest rbit-lens) 333 | ) 334 | ) 335 | ) 336 | ) 337 | ) 338 | ) 339 | 340 | (defn bin [x] 341 | "Returns x as a binary number in a string" 342 | (.toString (bigint x) 2) 343 | ) 344 | --------------------------------------------------------------------------------