binaryio: Functions for Reading and Writing Binary Data
This library provides functions for reading, writing, and converting binary data. It is designed for the use case of implementing network protocols, although this library focuses on low-level support, not high-level protocol specification.
(require binaryio) | package: binaryio-lib |
This module combines the exports of binaryio/bytes, binaryio/integer, and binaryio/float.
1 Bytes
(require binaryio/bytes) | package: binaryio-lib |
procedure
(read-bytes* len [in]) → bytes?
len : exact-nonnegative-integer? in : input-port? = (current-input-port)
> (define in (open-input-bytes #"abcde")) > (read-bytes* 4 in) #"abcd"
> (read-bytes* 2 in) read-bytes*: unexpected end of input
tried to read: 2 bytes
available: 1 bytes
received: #"e"
procedure
(write-null-terminated-bytes bstr [ out start end]) → void? bstr : bytes? out : output-port? = (current-output-port) start : exact-nonnegative-integer? = 0 end : exact-nonnegative-integer? = (bytes-length bstr)
If bstr contains any null bytes between start and end, an error is raised.
procedure
(read-null-terminated-bytes [in]) → bytes?
in : input-port? = (current-input-port)
> (define-values (in out) (make-pipe)) > (write-null-terminated-bytes #"abcde" out) > (read-null-terminated-bytes in) #"abcde"
2 Integers
(require binaryio/integer) | package: binaryio-lib |
procedure
(integer->bytes val size signed? [ big-endian? dest start]) → bytes? val : exact-integer? size : exact-positive-integer? signed? : boolean? big-endian? : boolean? = #t dest : (and/c bytes? (not/c mutable?)) = (make-bytes size) start : exact-nonnegative-integer? = 0
> (integer->bytes -1 3 #t) #"\377\377\377"
> (integer->bytes (expt 23 31) 18 #f) #"\22\305U2\375\302\332\26_\5\6\235q\30~\253\6\247"
procedure
(bytes->integer bstr signed? [ big-endian? start end]) → exact-integer? bstr : bytes? signed? : boolean? big-endian? : boolean? = #t start : exact-nonnegative-integer? = 0 end : exact-nonnegative-integer? = (bytes-length bstr)
procedure
(integer-bytes-length val signed?) → exact-nonnegative-integer?
val : exact-integer? signed? : boolean?
> (integer-bytes-length 127 #t) 1
> (integer-bytes-length 128 #t) 2
procedure
(integer-bytes-length<=? val nbytes signed?) → boolean?
val : exact-integer? nbytes : exact-nonnegative-integer? signed? : boolean?
procedure
(write-integer val size signed? [ out big-endian?]) → void? val : exact-integer? size : exact-positive-integer? signed? : boolean? out : output-port? = (current-output-port) big-endian? : boolean? = #t
Equivalent to (write-bytes (integer->bytes val size signed? big-endian?) out).
procedure
(read-integer size signed? [in big-endian?]) → exact-integer?
size : exact-positive-integer? signed? : boolean? in : input-port? = (current-input-port) big-endian? : boolean? = #t
Equivalent to (bytes->integer (read-bytes* size in) signed? big-endian?).
3 Floating-point
(require binaryio/float) | package: binaryio-lib |
procedure
(write-float val size [out big-endian?]) → void?
val : real? size : (or/c 4 8) out : output-port? = (current-output-port) big-endian? : boolean? = #t
procedure
(read-float size [in big-endian?]) → real?
size : (or/c 4 8) in : input-port? = (current-input-port) big-endian? : boolean? = #t
4 Binary Reader
(require binaryio/reader) | package: binaryio-lib |
Added in version 1.1 of package binaryio-lib.
procedure
(make-binary-reader in [ #:limit limit #:error-handler error-handler]) → binary-reader? in : input-port? limit : (or/c exact-nonnegative-integer? #f) = #f error-handler : (binary-reader-error-handler? #f) = #f
Convenience functions for reading binary encodings of integers and floating-point numbers of different lengths, endianness, etc.
The error-handler hook for customizing error message. See make-binary-reader-error-handler for details.
Automatic handling of short reads. If in returns eof or fewer bytes than requested in a read operation on the binary reader, the error-handler is used to raise an error. Thus, for example, the caller of (b-read-bytes br len) can rely on receiving a bytestring of exactly len bytes.
A stack of limits, maintained with b-push-limit and b-pop-limit. On every read operation, the limits are decremented by the number of bytes read. If a read operation requests more bytes than the current limit, the error-handler is used to raise an error.
Binary readers are not thread-safe. Be careful when interleaving uses of a binary reader with direct uses of its input port. For example, direct reads from in do not count against limits imposed on the binary reader.
procedure
(binary-reader? v) → boolean?
v : any/c
procedure
(make-binary-reader-error-handler [ #:error error-callback #:show-data? show-data?-callback]) → binary-reader-error-handler?
error-callback : (or/c #f (->* [binary-reader? symbol? string?] [] #:rest list? none/c)) = #f
show-data?-callback : (or/c #f (-> binary-reader? symbol? boolean?)) = #f
When an error occurs, the error-callback is called with the binary reader, the name of the function that raised the error, and a format string and arguments for the error message. If error-callback is #f, the error procedure is called instead (omitting the binary reader argument). The error-callback must escape, typically by throwing an exception; if it returns an exception is raised.
When a short read occurs, the show-data?-callback determines whether the error message contains the data actually read.
procedure
v : any/c
procedure
(b-get-limit br) → (or/c exact-nonnegative-integer? #f)
br : binary-reader?
procedure
(b-at-limit? br) → boolean?
br : binary-reader?
procedure
(b-at-limit/eof? br) → boolean?
br : binary-reader?
procedure
(b-push-limit br limit) → void?
br : binary-reader? limit : exact-nonnegative-integer?
procedure
(b-pop-limit br) → void?
br : binary-reader?
procedure
(b-call/save-limit br proc) → any
br : binary-reader? proc : (-> any)
Added in version 1.2 of package binaryio-lib.
procedure
(b-check-exhausted br what) → void?
br : binary-reader? what : (or/c string? #f)
procedure
(b-read-bytes br len) → bytes?
br : binary-reader? len : exact-nonnegative-integer?
procedure
(b-read-bytes! br bs [start end]) → exact-nonnegative-integer?
br : binary-reader? bs : bytes? start : exact-nonnegative-integer? = 0 end : exact-nonnegative-integer? = (bytes-length bs)
procedure
(b-read-byte br) → byte?
br : binary-reader?
procedure
(b-read-integer br size signed? [big-endian?]) → exact-integer?
br : binary-reader? size : exact-positive-integer? signed? : boolean? big-endian? : boolean? = #t
procedure
(b-read-float br size [big-endian?]) → real?
br : binary-reader? size : (or/c 4 8) big-endian? : boolean? = #t
procedure
(b-read-be-int br size) → exact-integer?
br : binary-reader? size : exact-positive-integer?
procedure
(b-read-be-uint br size) → exact-nonnegative-integer?
br : binary-reader? size : exact-positive-integer?
procedure
(b-read-le-int br size) → exact-integer?
br : binary-reader? size : exact-positive-integer?
procedure
(b-read-le-uint br size) → exact-integer?
br : binary-reader? size : exact-positive-integer?
procedure
br : binary-reader?
procedure
(b-read-bytes-line+eol br eol-mode) →
bytes? bytes? br : binary-reader? eol-mode : (or/c 'linefeed 'return 'return-linefeed 'any 'any-one)
Added in version 1.2 of package binaryio-lib.
procedure
(b-read-bytes-line br eol-mode) → bytes?
br : binary-reader? eol-mode : (or/c 'linefeed 'return 'return-linefeed 'any 'any-one)
Added in version 1.2 of package binaryio-lib.
5 Fixup Ports
Added in version 1.1 of package binaryio-lib.
(require binaryio/fixup-port) | package: binaryio-lib |
procedure
Operations on fixup ports are not thread-safe.
procedure
(fixup-port? v) → boolean?
v : any/c
procedure
(push-fixup fp [size]) → void?
fp : fixup-port? size : (or/c exact-positive-integer? #f) = #f
procedure
fp : fixup-port? fixup : (-> exact-nonnegative-integer? bytes?)
If the fixup was created with size size, then (fixup len) must return exactly size bytes, otherwise an error is raised.
procedure
(fixup-port-flush fp out) → void?
fp : fixup-port? out : output-port?
6 Short Bitvectors
(require binaryio/bitvector) | package: binaryio-lib |
Added in version 1.2 of package binaryio-lib.
(+ L (* b_0 (arithmetic-shift 1 (+ 0 SBV-LENGTH-BITS))) (* b_1 (arithmetic-shift 1 (+ 1 SBV-LENGTH-BITS))) (* b_2 (arithmetic-shift 1 (+ 2 SBV-LENGTH-BITS))) ... (* b_L-1 (arithmetic-shift 1 (+ L-1 SBV-LENGTH-BITS))))
Consequently, bitvectors up to about 46 bits are represented using fixnums (on 64-bit versions of Racket), and only bitvectors up to (sub1 (expt 2 SBV-LENGTH-BITS)) bits are representable.
value
value
SBV-LENGTH-BOUND : exact-nonnegative-integer? = 65536
Equivalent to exact-nonnegative-integer?. See also canonical-sbv?.
procedure
(canonical-sbv? v) → boolean?
v : any/c
For example, (make-sbv #b1011 2) is not canonical because it has a bit set after the first two bits.
Warning: In general, the functions in this library may produce bad results if given non-canonical bitvector values.
procedure
le-bits : exact-nonnegative-integer? bitlength : exact-nonnegative-integer?
If le-bits has a bit set after the first bitlength bits, then the result is non-canonical (see canonical-sbv?). If bitlength is not less then SBV-LENGTH-BOUND, an error is raised.
> (sbv->string (make-sbv 6 3)) "011"
procedure
(make-be-sbv be-bits bitlength) → sbv?
be-bits : exact-nonnegative-integer? bitlength : exact-nonnegative-integer?
Equivalent to (sbv-reverse (make-sbv be-bits bitlength)).
> (sbv->string (make-be-sbv 6 3)) "110"
procedure
(sbv-empty? sbv) → boolean?
sbv : sbv?
procedure
(sbv-length sbv) → exact-nonnegative-integer?
sbv : sbv?
procedure
(sbv-bits sbv) → exact-nonnegative-integer?
sbv : sbv?
> (sbv-bits (string->sbv "1011")) 13
procedure
(sbv-bit-field sbv start end) → exact-nonnegative-integer?
sbv : sbv? start : exact-nonnegative-integer? end : exact-nonnegative-integer?
If end is greater than (sbv-length sbv), then the “out of range” bits are set to zero.
> (sbv-bit-field (string->sbv "11100") 1 4) 3
> (sbv-bit-field (string->sbv "11100") 1 10) 3
procedure
sbv : sbv? start : exact-nonnegative-integer? end : exact-nonnegative-integer?
If end is greater than (sbv-length sbv), then the “out of range” bits are set to zero.
> (sbv->string (sbv-slice (string->sbv "11100") 1 4)) "110"
> (sbv->string (sbv-slice (string->sbv "11100") 1 10)) "110000000"
procedure
sbv : sbv? lshift : exact-integer?
> (sbv->string (sbv-shift (string->sbv "11100") 3)) "00011100"
> (sbv->string (sbv-shift (string->sbv "11100") -2)) "100"
procedure
(sbv-bit-set? sbv index) → boolean?
sbv : sbv? index : exact-nonnegative-integer?
> (sbv-bit-set? (string->sbv "1101") 0) #t
> (sbv-bit-set? (string->sbv "1101") 1) #t
> (sbv-bit-set? (string->sbv "1101") 2) #f
procedure
sbv : sbv? index : exact-nonnegative-integer?
> (sbv-ref (string->sbv "1101") 0) 1
> (sbv-ref (string->sbv "1101") 1) 1
> (sbv-ref (string->sbv "1101") 2) 0
Equivalent to (sbv-ref sbv 0) and (sbv-shift sbv -1).
> (sbv-car (string->sbv "110")) 1
> (sbv->string (sbv-cdr (string->sbv "110"))) "10"
procedure
(sbv-append sbv ...) → sbv?
sbv : sbv?
> (sbv->string (sbv-append (string->sbv "1011") (string->sbv "00"))) "101100"
Equivalent to (sbv-append (make-sbv bit 1) sbv).
procedure
(sbv-reverse sbv) → sbv?
sbv : sbv?
> (sbv->string (sbv-reverse (string->sbv "1101"))) "1011"
procedure
(sbv-prefix? sbv1 sbv2) → boolean?
sbv1 : sbv? sbv2 : sbv?
> (sbv-prefix? (string->sbv "110") (string->sbv "110")) #t
> (sbv-prefix? (string->sbv "110") (string->sbv "1100")) #t
> (sbv-prefix? (string->sbv "110") (string->sbv "100110")) #f
> (sbv-prefix? (string->sbv "110") (string->sbv "11")) #f
procedure
(sbv->string sbv) → string?
sbv : sbv?
> (sbv->string (sbv-append (make-sbv 1 1) (make-sbv 1 1) (make-sbv 0 1))) "110"
(make-be-sbv (string->number (sbv->string sbv) 2) (sbv-length sbv))
procedure
(string->sbv s) → sbv?
s : (and/c string? #rx"^[01]*$")
7 Bitports
(require binaryio/bitport) | package: binaryio-lib |
Added in version 1.2 of package binaryio-lib.
An output bitport is like an output string port (open-output-bytes), except that instead of accumulating bytes, it accumulates bits and packs them into a byte string.
procedure
(output-bitport? v) → boolean?
v : any/c
procedure
procedure
(output-bitport-partial bp) → sbv?
bp : output-bitport?
procedure
(output-bitport-write-bit bp bit) → void?
bp : output-bitport? bit : (or/c 0 1)
Equivalent to (output-bitport-write-sbv (make-sbv bit 1)).
procedure
(output-bitport-write-sbv bp sbv) → void?
bp : output-bitport? sbv : sbv?
procedure
(output-bitport-get-output bp [ #:reset? reset? #:pad pad-sbv])
→
bytes? exact-nonnegative-integer? bp : output-bitport? reset? : boolean? = #f pad-sbv : sbv? = empty-sbv
If bp contains an incomplete byte (because bits-written is not divisible by 8), then the final byte of the output is padded with the lower bits of pad-sbv (extended with zeros if more padding bits are needed). If bits-written is divisible by 8, no padding is included.
If reset? is true, then all written bits are removed from bp.
procedure
(output-bitport-pad bp [#:pad pad-sbv])
→ exact-nonnegative-integer? bp : output-bitport? pad-sbv : sbv? = 0
procedure
(bytes-bit-set? bs bit-index) → boolean?
bs : bytes? bit-index : exact-nonnegative-integer?
The byte string is interpreted as big-endian in the following sense: within a single byte in bs, bits are indexed started with the most significant bit first. So for example, (bytes-bit-set? (bytes b) 0) is (bitwise-bit-set? b 7).
> (bytes-bit-set? (bytes 1 128) 0) #f
> (bytes-bit-set? (bytes 1 128) 7) #t
> (bytes-bit-set? (bytes 1 128) 8) #t
> (bytes-bit-set? (bytes 1 128) 15) #f
8 Prefix Codes: Encoding and Decoding
(require binaryio/prefixcode) | package: binaryio-lib |
Added in version 1.2 of package binaryio-lib.
This module provides encoding and decoding support using prefix codes (aka prefix-free codes), including Huffman codes. It does not implement algorithms for building such codes.
procedure
(prefixcode-encode encode-table input [ #:pad pad-sbv])
→
bytes? exact-nonnegative-integer?
encode-table :
(or/c (hash/c any/c sbv?) (listof (cons/c any/c sbv?)) (vectorof sbv?)) input : sequence? pad-sbv : sbv? = empty-sbv
> (require binaryio/examples/hpack)
> (define-values (enc enc-bits) (prefixcode-encode hpack-encode-table #"hello world!" #:pad hpack-end-code)) > (values enc enc-bits)
#"\234\264Pu<\36\312$\376?"
74
procedure
(prefixcode-encode! bp encode-table input [ #:pad pad-sbv]) → void? bp : output-bitport?
encode-table :
(or/c (hash/c any/c sbv?) (listof (cons/c any/c sbv?)) (vectorof sbv?)) input : sequence? pad-sbv : sbv? = empty-sbv
procedure
(prefixcode-build-decode-tree encode-table) → any/c
encode-table :
(or/c (hash/c any/c sbv?) (listof (cons/c any/c sbv?)) (vectorof sbv?))
The representation is not specified, but if all values in the table are readable (or quotable), then the representation of the decoder tree is readable (or quotable).
> (define hpack-decode-tree (prefixcode-build-decode-tree hpack-encode-table))
procedure
(prefixcode-decode decode-tree bs [ start-bit-index end-bit-index #:end end-code #:handle-error handle-error]) → bytes? decode-tree : any/c bs : bytes? start-bit-index : exact-nonnegative-integer? = 0
end-bit-index : exact-nonnegative-integer? = (* 8 (bytes-length bs)) end-code : (or/c sbv? #f) = #f
handle-error :
(-> (or/c 'bad 'incomplete) exact-nonnegative-integer? exact-nonnegative-integer? sbv? any) = (lambda (mode start end code) (error ....))
Each value represented by decode-tree must be a byte, character, byte string, or character string. Each decoded value is accumulated into a byte string, which is the result of successful decoding.
(handle-error 'bad bad-start-index bad-end-index bad-code)
(handle-error 'incomplete incomplete-start-index end-bit-index incomplete-code)
Note that if handle-error returns normally, its result is discarded, so it is recommended that handle-error escape (for example, by raising an exception).
> (prefixcode-decode hpack-decode-tree enc 0 enc-bits) #"hello world!"
> (prefixcode-decode hpack-decode-tree enc #:end hpack-end-code) #"hello world!"
> (prefixcode-decode hpack-decode-tree enc #:handle-error list) #"hello world!"
procedure
(prefixcode-decode! output decode-tree bs [ start-bit-index end-bit-index #:end end-code #:handle-error handle-error]) → (or/c void? any) output : (or/c output-port? (-> any/c void?)) decode-tree : any/c bs : bytes? start-bit-index : exact-nonnegative-integer? = 0
end-bit-index : exact-nonnegative-integer? = (* 8 (bytes-length bs)) end-code : (or/c sbv? #f) = #f
handle-error :
(-> (or/c 'bad 'incomplete) exact-nonnegative-integer? exact-nonnegative-integer? sbv? any) = (lambda (mode start end code) (error ....))
If output is an output port, then a decoded value must be a byte, character, byte string, or character string, and the value is emitted by writing it to the port. If output is a procedure, then any value is allowed, and the value is emitted by calling output on it.
If decoding completes successfully, the result is (void); otherwise, it is the result of the call to handle-error.
> (call-with-output-bytes (lambda (out) (prefixcode-decode! out hpack-decode-tree enc 0 enc-bits))) #"hello world!"
> (call-with-output-bytes (lambda (out) (prefixcode-decode! out hpack-decode-tree enc 0 #:end hpack-end-code))) #"hello world!"
> (prefixcode-decode! void hpack-decode-tree enc #:handle-error list) '(incomplete 74 80 4128774)
procedure
(prefixcode-decode1 decode-tree bs [ start-bit-index end-bit-index #:end end-code])
→
(or/c 'ok 'bad 'end 'incomplete) exact-nonnegative-integer? any/c decode-tree : any/c bs : bytes? start-bit-index : exact-nonnegative-integer? = 0
end-bit-index : exact-nonnegative-integer? = (* 8 (bytes-length bs)) end-code : (or/c sbv? #f) = #f
(values 'ok next-bit-index value) —
the bits from start-bit-index to next-bit-index represent the value value (values 'bad next-bit-index bad-code) —
the bits from start-bit-index to next-bit-index do not represent a valid code (or its prefix); bad-code contains those bits as a bitvector (values 'end next-bit-index incomplete-code) —
the bits from start-bit-index to next-bit-index represent an incomplete prefix of end-code; incomplete-code contains those bits as a bitvector (values 'incomplete next-bit-index incomplete-code) —
the bits from start-bit-index to next-bit-index represent an incomplete code, but it is not a prefix of end-code; incomplete-code contains those bits as a bitvector
> (prefixcode-decode1 hpack-decode-tree enc 0)
'ok
6
104
> (prefixcode-decode1 hpack-decode-tree enc 6)
'ok
11
101
> (bytes 104 101) #"he"