Initial commit; import from github.com/lwithers/pkg
This commit is contained in:
commit
08044ce5ef
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2020 Laurence Withers
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,57 @@
|
||||||
|
# src.lwithers.me.uk/go/byteio
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/src.lwithers.me.uk/go/byteio?status.svg)](https://godoc.org/src.lwithers.me.uk/go/byteio)
|
||||||
|
|
||||||
|
This package provides two interfaces, `Reader` and `Writer`, which capture the
|
||||||
|
most common byte-oriented methods for I/O, namely `Read([]byte)`, `ReadByte()`
|
||||||
|
and `ReadRune()` (plus the equivalent writing methods). This is useful because
|
||||||
|
sometimes you simply want to perform byte-oriented I/O without knowing whether
|
||||||
|
you have an underlying `bytes.Buffer` or `bufio.Reader` etc.
|
||||||
|
|
||||||
|
Furthermore, as a convenience, there are functions which automatically wrap
|
||||||
|
any `io.Reader` or `io.Writer` with a bufio equivalent if necessary. These
|
||||||
|
allow for simple byte-oriented I/O operations on arbitrary reader / writer
|
||||||
|
interfaces with minimal boilerplate.
|
||||||
|
|
||||||
|
Example of a method using the `byteio.Reader` interface:
|
||||||
|
|
||||||
|
```
|
||||||
|
func ReadUint16LE(r byteio.Reader) (uint16, error) {
|
||||||
|
var n uint16
|
||||||
|
if x, err := r.ReadByte(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else {
|
||||||
|
n = uint16(x)
|
||||||
|
}
|
||||||
|
if x, err := r.ReadByte(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else {
|
||||||
|
n |= uint16(x) << 8
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Example of something calling this function:
|
||||||
|
|
||||||
|
```
|
||||||
|
var r io.Reader
|
||||||
|
// …
|
||||||
|
br := byteio.NewReader(r)
|
||||||
|
n, err := ReadUint16LE(br)
|
||||||
|
```
|
||||||
|
|
||||||
|
Example of using the writer interface:
|
||||||
|
|
||||||
|
```
|
||||||
|
var w io.Writer
|
||||||
|
// …
|
||||||
|
bw := byteio.NewWriter(w)
|
||||||
|
defer byteio.FlushIfNecessary(bw)
|
||||||
|
err := WriteUint16LE(bw, 0x5432)
|
||||||
|
```
|
||||||
|
|
||||||
|
The `byteio.FlushIfNecessary` method determines whether its argument has a
|
||||||
|
`Flush()` method or not, and if it does, calls it. This allows the caller to
|
||||||
|
write non-conditional code, not having to check whether `bw` is a `bufio.Writer`
|
||||||
|
(which needs a flush) or a `bytes.Buffer` (which does not), etc.
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
Package byteio supports applications that perform byte-oriented I/O, reading and
|
||||||
|
writing only small chunks at a time. Its primary purpose is the Reader and
|
||||||
|
Writer interfaces which extend io.Reader and io.Writer with byte-, rune- and
|
||||||
|
string-oriented functions, along with an adapter function that turns any reader
|
||||||
|
or writer into the interface (possibly by wrapping it in a bufio.Reader or
|
||||||
|
bufio.Writer).
|
||||||
|
|
||||||
|
Note that the Reader and Writer types from both the bufio and the bytes package
|
||||||
|
implement this package's Reader and Writer interfaces.
|
||||||
|
|
||||||
|
When using the adapter function for the writer, since it is possible that the
|
||||||
|
returned type may be a new bufio.Writer, it is necessary to check for whether
|
||||||
|
Flush() must be called at operation completion time. This can be done
|
||||||
|
succinctly with:
|
||||||
|
|
||||||
|
bout := byteio.NewWriter(out)
|
||||||
|
defer byteio.FlushIfNecessary(bout)
|
||||||
|
|
||||||
|
The binary read/write functions were benchmarked (using bufio) to determine that,
|
||||||
|
for sizes up to and including 8 bytes, it was faster to call
|
||||||
|
ReadByte()/WriteByte() multiple times in succession than to call Read()/Write()
|
||||||
|
with a small buffer.
|
||||||
|
*/
|
||||||
|
package byteio
|
|
@ -0,0 +1,64 @@
|
||||||
|
package byteio_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"src.lwithers.me.uk/go/byteio"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SwapEndian32 swaps the endianness of a stream of uint32 integers.
|
||||||
|
//
|
||||||
|
// Note this function takes standard io.Reader and io.Writer interfaces, and
|
||||||
|
// uses byteio to (possibly) wrap these with bufio.Reader/Writer for efficient
|
||||||
|
// byte-oriented operation. This is transparent to the caller.
|
||||||
|
func SwapEndian32(in io.Reader, out io.Writer) error {
|
||||||
|
// optionally wrap input and output
|
||||||
|
bin, bout := byteio.NewReader(in), byteio.NewWriter(out)
|
||||||
|
|
||||||
|
// since output may be buffered, make sure to flush it when leaving
|
||||||
|
// this function
|
||||||
|
defer byteio.FlushIfNecessary(bout)
|
||||||
|
|
||||||
|
for {
|
||||||
|
// it doesn't matter whether we read BE or LE, as long as we
|
||||||
|
// write the opposite!
|
||||||
|
n, err := byteio.ReadUint32BE(bin)
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
case io.EOF:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = byteio.WriteUint32LE(bout, n); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example() {
|
||||||
|
// prepare input buffer
|
||||||
|
input := []uint32{0xDEADBEEF, 0x7FFFFFFF}
|
||||||
|
inbuf := bytes.NewBuffer(nil)
|
||||||
|
for _, n := range input {
|
||||||
|
byteio.WriteUint32BE(inbuf, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// run our endianness swapper
|
||||||
|
outbuf := bytes.NewBuffer(nil)
|
||||||
|
if err := SwapEndian32(inbuf, outbuf); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dump the output
|
||||||
|
fmt.Println(hex.Dump(outbuf.Bytes()))
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// 00000000 ef be ad de ff ff ff 7f |........|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package byteio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reader provides byte-oriented reading routines. It is satisfied by
|
||||||
|
// bufio.Reader, bytes.Reader and bytes.Buffer.
|
||||||
|
type Reader interface {
|
||||||
|
io.Reader
|
||||||
|
io.ByteReader
|
||||||
|
io.RuneReader
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReader adapts any io.Reader into a byteio.Reader, possibly returning
|
||||||
|
// a new bufio.Reader.
|
||||||
|
func NewReader(in io.Reader) Reader {
|
||||||
|
if bin, ok := in.(Reader); ok {
|
||||||
|
return bin
|
||||||
|
}
|
||||||
|
return bufio.NewReader(in)
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package byteio_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"src.lwithers.me.uk/go/byteio"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockReader does not implement Reader, thus ensuring that NewReader must wrap
|
||||||
|
// it. The implementation returns an incrementing byte pattern.
|
||||||
|
type MockReader struct {
|
||||||
|
pos uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MockReader) Read(buf []byte) (n int, err error) {
|
||||||
|
for i := range buf {
|
||||||
|
buf[i] = r.pos
|
||||||
|
r.pos++
|
||||||
|
}
|
||||||
|
return len(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewReaderBytesB checks that a bytes.Buffer is successfully transformed
|
||||||
|
// into a Reader.
|
||||||
|
func TestNewReaderBytesB(t *testing.T) {
|
||||||
|
orig := bytes.NewBuffer(nil)
|
||||||
|
bin := byteio.NewReader(orig)
|
||||||
|
if act, ok := bin.(*bytes.Buffer); !ok {
|
||||||
|
t.Errorf("Reader(%T) returned unexpected %T", orig, bin)
|
||||||
|
} else if act != orig {
|
||||||
|
t.Errorf("Reader(%p) returned unexpected %p", orig, act)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewReaderBytesR checks that a bytes.Reader is successfully transformed
|
||||||
|
// into a Reader.
|
||||||
|
func TestNewReaderBytesR(t *testing.T) {
|
||||||
|
orig := bytes.NewReader(nil)
|
||||||
|
bin := byteio.NewReader(orig)
|
||||||
|
if act, ok := bin.(*bytes.Reader); !ok {
|
||||||
|
t.Errorf("Reader(%T) returned unexpected %T", orig, bin)
|
||||||
|
} else if act != orig {
|
||||||
|
t.Errorf("Reader(%p) returned unexpected %p", orig, act)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewReaderBufio checks that a bufio.Reader is successfully transformed
|
||||||
|
// into a Reader.
|
||||||
|
func TestNewReaderBufio(t *testing.T) {
|
||||||
|
orig := bufio.NewReader(os.Stdin)
|
||||||
|
bin := byteio.NewReader(orig)
|
||||||
|
if act, ok := bin.(*bufio.Reader); !ok {
|
||||||
|
t.Errorf("Reader(%T) returned unexpected %T", orig, bin)
|
||||||
|
} else if act != orig {
|
||||||
|
t.Errorf("Reader(%p) returned unexpected %p", orig, act)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewReader checks that an arbitrary io.Reader is successfully wrapped into
|
||||||
|
// a byteio.Reader.
|
||||||
|
func TestNewReader(t *testing.T) {
|
||||||
|
orig := new(MockReader)
|
||||||
|
bin := byteio.NewReader(orig)
|
||||||
|
if _, ok := bin.(*bufio.Reader); !ok {
|
||||||
|
t.Errorf("Reader(%T) did not wrap to bufio.Reader (got %T)",
|
||||||
|
orig, bin)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,271 @@
|
||||||
|
package byteio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadUint16BE reads an unsigned uint16 in big-endian (network) byte order.
|
||||||
|
func ReadUint16BE(bin Reader) (uint16, error) {
|
||||||
|
var b0, b1 byte
|
||||||
|
var err error
|
||||||
|
if b0, err = bin.ReadByte(); err != nil {
|
||||||
|
// allow io.EOF to propagate normally before first read
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if b1, err = bin.ReadByte(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return uint16(b0)<<8 | uint16(b1), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadInt16BE reads a signed int16 in big-endian (network) byte order.
|
||||||
|
func ReadInt16BE(bin Reader) (int16, error) {
|
||||||
|
n, err := ReadUint16BE(bin)
|
||||||
|
return int16(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUint32BE reads an unsigned uint32 in big-endian (network) byte order.
|
||||||
|
func ReadUint32BE(bin Reader) (uint32, error) {
|
||||||
|
var b0, b1, b2, b3 byte
|
||||||
|
var err error
|
||||||
|
if b0, err = bin.ReadByte(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if b1, err = bin.ReadByte(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if b2, err = bin.ReadByte(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if b3, err = bin.ReadByte(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return uint32(b0)<<24 | uint32(b1)<<16 |
|
||||||
|
uint32(b2)<<8 | uint32(b3), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadInt32BE reads a signed int32 in big-endian (network) byte order.
|
||||||
|
func ReadInt32BE(bin Reader) (int32, error) {
|
||||||
|
n, err := ReadUint32BE(bin)
|
||||||
|
return int32(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFloat32BE reads an IEEE-754 32-bit floating point number in big-endian
|
||||||
|
// (network) byte order.
|
||||||
|
func ReadFloat32BE(bin Reader) (float32, error) {
|
||||||
|
n, err := ReadUint32BE(bin)
|
||||||
|
return math.Float32frombits(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUint64BE reads an unsigned uint64 in big-endian (network) byte order.
|
||||||
|
func ReadUint64BE(bin Reader) (uint64, error) {
|
||||||
|
var b0, b1, b2, b3, b4, b5, b6, b7 byte
|
||||||
|
var err error
|
||||||
|
if b0, err = bin.ReadByte(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if b1, err = bin.ReadByte(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if b2, err = bin.ReadByte(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if b3, err = bin.ReadByte(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if b4, err = bin.ReadByte(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if b5, err = bin.ReadByte(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if b6, err = bin.ReadByte(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if b7, err = bin.ReadByte(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return uint64(b0)<<56 | uint64(b1)<<48 |
|
||||||
|
uint64(b2)<<40 | uint64(b3)<<32 |
|
||||||
|
uint64(b4)<<24 | uint64(b5)<<16 |
|
||||||
|
uint64(b6)<<8 | uint64(b7), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadInt64BE reads a signed int64 in big-endian (network) byte order.
|
||||||
|
func ReadInt64BE(bin Reader) (int64, error) {
|
||||||
|
n, err := ReadUint64BE(bin)
|
||||||
|
return int64(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFloat64BE reads an IEEE-754 64-bit floating point number in big-endian
|
||||||
|
// (network) byte order.
|
||||||
|
func ReadFloat64BE(bin Reader) (float64, error) {
|
||||||
|
n, err := ReadUint64BE(bin)
|
||||||
|
return math.Float64frombits(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUint16LE reads an unsigned uint16 in little-endian byte order.
|
||||||
|
func ReadUint16LE(bin Reader) (uint16, error) {
|
||||||
|
var b0, b1 byte
|
||||||
|
var err error
|
||||||
|
if b1, err = bin.ReadByte(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if b0, err = bin.ReadByte(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return uint16(b0)<<8 | uint16(b1), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUint16LE reads a signed int16 in little-endian byte order.
|
||||||
|
func ReadInt16LE(bin Reader) (int16, error) {
|
||||||
|
n, err := ReadUint16LE(bin)
|
||||||
|
return int16(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUint32LE reads an unsigned uint32 in little-endian byte order.
|
||||||
|
func ReadUint32LE(bin Reader) (uint32, error) {
|
||||||
|
var b0, b1, b2, b3 byte
|
||||||
|
var err error
|
||||||
|
if b3, err = bin.ReadByte(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if b2, err = bin.ReadByte(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if b1, err = bin.ReadByte(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if b0, err = bin.ReadByte(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return uint32(b0)<<24 | uint32(b1)<<16 |
|
||||||
|
uint32(b2)<<8 | uint32(b3), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUint32LE reads a signed int32 in little-endian byte order.
|
||||||
|
func ReadInt32LE(bin Reader) (int32, error) {
|
||||||
|
n, err := ReadUint32LE(bin)
|
||||||
|
return int32(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFloat32LE reads an IEEE-754 32-bit floating point number in big-endian
|
||||||
|
// (network) byte order.
|
||||||
|
func ReadFloat32LE(bin Reader) (float32, error) {
|
||||||
|
n, err := ReadUint32LE(bin)
|
||||||
|
return math.Float32frombits(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUint64LE reads an unsigned uint64 in little-endian byte order.
|
||||||
|
func ReadUint64LE(bin Reader) (uint64, error) {
|
||||||
|
var b0, b1, b2, b3, b4, b5, b6, b7 byte
|
||||||
|
var err error
|
||||||
|
if b7, err = bin.ReadByte(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if b6, err = bin.ReadByte(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if b5, err = bin.ReadByte(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if b4, err = bin.ReadByte(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if b3, err = bin.ReadByte(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if b2, err = bin.ReadByte(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if b1, err = bin.ReadByte(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if b0, err = bin.ReadByte(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return uint64(b0)<<56 | uint64(b1)<<48 |
|
||||||
|
uint64(b2)<<40 | uint64(b3)<<32 |
|
||||||
|
uint64(b4)<<24 | uint64(b5)<<16 |
|
||||||
|
uint64(b6)<<8 | uint64(b7), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadInt64LE reads a signed int64 in little-endian byte order.
|
||||||
|
func ReadInt64LE(bin Reader) (int64, error) {
|
||||||
|
n, err := ReadUint64LE(bin)
|
||||||
|
return int64(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFloat64LE reads an IEEE-754 64-bit floating point number in big-endian
|
||||||
|
// (network) byte order.
|
||||||
|
func ReadFloat64LE(bin Reader) (float64, error) {
|
||||||
|
n, err := ReadUint64LE(bin)
|
||||||
|
return math.Float64frombits(n), err
|
||||||
|
}
|
|
@ -0,0 +1,329 @@
|
||||||
|
package byteio_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"src.lwithers.me.uk/go/byteio"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestReadUint ensures correct operation of the ReadUint variants by verifying
|
||||||
|
// read data integrity against encoding/binary.
|
||||||
|
func TestReadUint(t *testing.T) {
|
||||||
|
checkErr := func(err error) {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected I/O error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare a buffer of test data
|
||||||
|
vals := []uint64{0, 1, 0x8000, 0xFFFF, 0x81020304050607}
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
for _, val := range vals {
|
||||||
|
checkErr(binary.Write(buf, binary.BigEndian, uint16(val)))
|
||||||
|
checkErr(binary.Write(buf, binary.BigEndian, uint32(val)))
|
||||||
|
checkErr(binary.Write(buf, binary.BigEndian, uint64(val)))
|
||||||
|
checkErr(binary.Write(buf, binary.LittleEndian, uint16(val)))
|
||||||
|
checkErr(binary.Write(buf, binary.LittleEndian, uint32(val)))
|
||||||
|
checkErr(binary.Write(buf, binary.LittleEndian, uint64(val)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate that we read back the expected values
|
||||||
|
for _, exp64 := range vals {
|
||||||
|
var (
|
||||||
|
act16, exp16 uint16 = 0, uint16(exp64)
|
||||||
|
act32, exp32 uint32 = 0, uint32(exp64)
|
||||||
|
act64 uint64
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
act16, err = byteio.ReadUint16BE(buf)
|
||||||
|
checkErr(err)
|
||||||
|
if act16 != exp16 {
|
||||||
|
t.Errorf("ReadUint16BE: act %X ≠ exp %X", act16, exp16)
|
||||||
|
}
|
||||||
|
|
||||||
|
act32, err = byteio.ReadUint32BE(buf)
|
||||||
|
checkErr(err)
|
||||||
|
if act32 != exp32 {
|
||||||
|
t.Errorf("ReadUint32BE: act %X ≠ exp %X", act32, exp32)
|
||||||
|
}
|
||||||
|
|
||||||
|
act64, err = byteio.ReadUint64BE(buf)
|
||||||
|
checkErr(err)
|
||||||
|
if act64 != exp64 {
|
||||||
|
t.Errorf("ReadUint64BE: act %X ≠ exp %X", act64, exp64)
|
||||||
|
}
|
||||||
|
|
||||||
|
act16, err = byteio.ReadUint16LE(buf)
|
||||||
|
checkErr(err)
|
||||||
|
if act16 != exp16 {
|
||||||
|
t.Errorf("ReadUint16LE: act %X ≠ exp %X", act16, exp16)
|
||||||
|
}
|
||||||
|
|
||||||
|
act32, err = byteio.ReadUint32LE(buf)
|
||||||
|
checkErr(err)
|
||||||
|
if act32 != exp32 {
|
||||||
|
t.Errorf("ReadUint32LE: act %X ≠ exp %X", act32, exp32)
|
||||||
|
}
|
||||||
|
|
||||||
|
act64, err = byteio.ReadUint64LE(buf)
|
||||||
|
checkErr(err)
|
||||||
|
if act64 != exp64 {
|
||||||
|
t.Errorf("ReadUint64LE: act %X ≠ exp %X", act64, exp64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestReadInt ensures correct operation of the ReadInt variants by comparing
|
||||||
|
// operation against encoding/binary.
|
||||||
|
func TestReadInt(t *testing.T) {
|
||||||
|
checkErr := func(err error) {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare a buffer of test data using encoding/binary
|
||||||
|
vals := []int64{-1, 0, 1, -0x8000, 0xFFFF, 0x01020304050607}
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
for _, val := range vals {
|
||||||
|
checkErr(binary.Write(buf, binary.BigEndian, int16(val)))
|
||||||
|
checkErr(binary.Write(buf, binary.BigEndian, int32(val)))
|
||||||
|
checkErr(binary.Write(buf, binary.BigEndian, int64(val)))
|
||||||
|
checkErr(binary.Write(buf, binary.LittleEndian, int16(val)))
|
||||||
|
checkErr(binary.Write(buf, binary.LittleEndian, int32(val)))
|
||||||
|
checkErr(binary.Write(buf, binary.LittleEndian, int64(val)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate that we read back the expected values
|
||||||
|
for _, exp64 := range vals {
|
||||||
|
var (
|
||||||
|
v16, exp16 int16 = 0, int16(exp64)
|
||||||
|
v32, exp32 int32 = 0, int32(exp64)
|
||||||
|
v64 int64
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
v16, err = byteio.ReadInt16BE(buf)
|
||||||
|
checkErr(err)
|
||||||
|
if v16 != exp16 {
|
||||||
|
t.Errorf("ReadInt16BE: act %X ≠ exp %X", v16, exp16)
|
||||||
|
}
|
||||||
|
|
||||||
|
v32, err = byteio.ReadInt32BE(buf)
|
||||||
|
checkErr(err)
|
||||||
|
if v32 != exp32 {
|
||||||
|
t.Errorf("ReadInt32BE: act %X ≠ exp %X", v32, exp32)
|
||||||
|
}
|
||||||
|
|
||||||
|
v64, err = byteio.ReadInt64BE(buf)
|
||||||
|
checkErr(err)
|
||||||
|
if v64 != exp64 {
|
||||||
|
t.Errorf("ReadInt64BE: act %X ≠ exp %X", v64, exp64)
|
||||||
|
}
|
||||||
|
|
||||||
|
v16, err = byteio.ReadInt16LE(buf)
|
||||||
|
checkErr(err)
|
||||||
|
if v16 != exp16 {
|
||||||
|
t.Errorf("ReadInt16LE: act %X ≠ exp %X", v16, exp16)
|
||||||
|
}
|
||||||
|
|
||||||
|
v32, err = byteio.ReadInt32LE(buf)
|
||||||
|
checkErr(err)
|
||||||
|
if v32 != exp32 {
|
||||||
|
t.Errorf("ReadInt32LE: act %X ≠ exp %X", v32, exp32)
|
||||||
|
}
|
||||||
|
|
||||||
|
v64, err = byteio.ReadInt64LE(buf)
|
||||||
|
checkErr(err)
|
||||||
|
if v64 != exp64 {
|
||||||
|
t.Errorf("ReadInt64LE: act %X ≠ exp %X", v64, exp64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestReadFloat validates operation of the byteio.ReadFloat variants against
|
||||||
|
// values written by encoding/binary.
|
||||||
|
func TestReadFloat(t *testing.T) {
|
||||||
|
vals := []float64{
|
||||||
|
0, 1, -1,
|
||||||
|
math.MaxFloat32, math.SmallestNonzeroFloat32,
|
||||||
|
math.Inf(1), math.Inf(-1), math.NaN(),
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
for _, val := range vals {
|
||||||
|
binary.Write(buf, binary.BigEndian, val)
|
||||||
|
binary.Write(buf, binary.BigEndian, float32(val))
|
||||||
|
binary.Write(buf, binary.LittleEndian, val)
|
||||||
|
binary.Write(buf, binary.LittleEndian, float32(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, exp64 := range vals {
|
||||||
|
exp32 := float32(exp64)
|
||||||
|
|
||||||
|
if act64, err := byteio.ReadFloat64BE(buf); err != nil {
|
||||||
|
t.Fatalf("read error: %v", err)
|
||||||
|
} else if act64 != exp64 && !math.IsNaN(act64) && !math.IsNaN(exp64) {
|
||||||
|
t.Errorf("act %f (0x%X) ≠ exp %f (0x%X)",
|
||||||
|
act64, math.Float64bits(act64),
|
||||||
|
exp64, math.Float64bits(exp64))
|
||||||
|
}
|
||||||
|
if act32, err := byteio.ReadFloat32BE(buf); err != nil {
|
||||||
|
t.Fatalf("read error: %v", err)
|
||||||
|
} else if act32 != exp32 && !math.IsNaN(float64(act32)) && !math.IsNaN(exp64) {
|
||||||
|
t.Errorf("act %f (0x%X) ≠ exp %f (0x%X)",
|
||||||
|
act32, math.Float32bits(act32),
|
||||||
|
exp32, math.Float32bits(exp32))
|
||||||
|
}
|
||||||
|
|
||||||
|
if act64, err := byteio.ReadFloat64LE(buf); err != nil {
|
||||||
|
t.Fatalf("read error: %v", err)
|
||||||
|
} else if act64 != exp64 && !math.IsNaN(act64) && !math.IsNaN(exp64) {
|
||||||
|
t.Errorf("act %f (0x%X) ≠ exp %f (0x%X)",
|
||||||
|
act64, math.Float64bits(act64),
|
||||||
|
exp64, math.Float64bits(exp64))
|
||||||
|
}
|
||||||
|
if act32, err := byteio.ReadFloat32LE(buf); err != nil {
|
||||||
|
t.Fatalf("read error: %v", err)
|
||||||
|
} else if act32 != exp32 && !math.IsNaN(float64(act32)) && !math.IsNaN(exp64) {
|
||||||
|
t.Errorf("act %f (0x%X) ≠ exp %f (0x%X)",
|
||||||
|
act32, math.Float32bits(act32),
|
||||||
|
exp32, math.Float32bits(exp32))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestReadIntEOF ensures that io.EOF is propagated correctly when a ReadInt
|
||||||
|
// variant is called at the end of a buffer (i.e. 0 bytes to read). It only
|
||||||
|
// tests the Uint variants since the Int and Float variants are wrappers over
|
||||||
|
// Uint.
|
||||||
|
func TestReadIntEOF(t *testing.T) {
|
||||||
|
bin := bytes.NewBuffer(nil)
|
||||||
|
check := func(fn string, f func() error) {
|
||||||
|
err := f()
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
t.Errorf("%s: did not return expected error", fn)
|
||||||
|
case io.EOF:
|
||||||
|
// OK
|
||||||
|
default:
|
||||||
|
t.Errorf("%s: returned unexpected error %v", fn, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check("ReadUint16LE", func() error { _, err := byteio.ReadUint16LE(bin); return err })
|
||||||
|
check("ReadUint16BE", func() error { _, err := byteio.ReadUint16BE(bin); return err })
|
||||||
|
check("ReadUint32LE", func() error { _, err := byteio.ReadUint32LE(bin); return err })
|
||||||
|
check("ReadUint32BE", func() error { _, err := byteio.ReadUint32BE(bin); return err })
|
||||||
|
check("ReadUint64LE", func() error { _, err := byteio.ReadUint64LE(bin); return err })
|
||||||
|
check("ReadUint64BE", func() error { _, err := byteio.ReadUint64BE(bin); return err })
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestReadIntShort ensures that io.ErrUnexpectedEOF is returned when a ReadInt
|
||||||
|
// variant is called without sufficient data, but not immediately at the end of
|
||||||
|
// the buffer (i.e. > 0 bytes to read, but not sufficient for data type).
|
||||||
|
func TestReadIntShort(t *testing.T) {
|
||||||
|
check := func(fn string, sz int, f func(bin byteio.Reader) error) {
|
||||||
|
for i := 1; i < sz; i++ {
|
||||||
|
bin := bytes.NewBuffer(make([]byte, i))
|
||||||
|
err := f(bin)
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
t.Errorf("%s/%d: did not return expected error",
|
||||||
|
fn, i)
|
||||||
|
case io.ErrUnexpectedEOF:
|
||||||
|
// OK
|
||||||
|
case io.EOF:
|
||||||
|
t.Errorf("%s/%d: returned incorrect io.EOF",
|
||||||
|
fn, i)
|
||||||
|
default:
|
||||||
|
t.Errorf("%s/%d: returned unexpected error %v",
|
||||||
|
fn, i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check("ReadUint16LE", 2, func(bin byteio.Reader) error { _, err := byteio.ReadUint16LE(bin); return err })
|
||||||
|
check("ReadUint16BE", 2, func(bin byteio.Reader) error { _, err := byteio.ReadUint16BE(bin); return err })
|
||||||
|
check("ReadUint32LE", 4, func(bin byteio.Reader) error { _, err := byteio.ReadUint32LE(bin); return err })
|
||||||
|
check("ReadUint32BE", 4, func(bin byteio.Reader) error { _, err := byteio.ReadUint32BE(bin); return err })
|
||||||
|
check("ReadUint64LE", 8, func(bin byteio.Reader) error { _, err := byteio.ReadUint64LE(bin); return err })
|
||||||
|
check("ReadUint64BE", 8, func(bin byteio.Reader) error { _, err := byteio.ReadUint64BE(bin); return err })
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrAbortReader is returned by AbortReader when the limit is reached.
|
||||||
|
var ErrAbortReader = errors.New("aborted read")
|
||||||
|
|
||||||
|
// AbortReader will abort after a certain number of bytes have been read with a
|
||||||
|
// non-io.EOF error. This can be used to test error handling on partial reads.
|
||||||
|
// It does not implement byteio.Reader, to ensure that errors propagate
|
||||||
|
// correctly through the implicit bufio wrapper.
|
||||||
|
type AbortReader struct {
|
||||||
|
when, cur int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ar *AbortReader) Read(buf []byte) (n int, err error) {
|
||||||
|
for i := range buf {
|
||||||
|
if ar.when == ar.cur {
|
||||||
|
return i, ErrAbortReader
|
||||||
|
}
|
||||||
|
buf[i] = 0x55
|
||||||
|
ar.cur++
|
||||||
|
}
|
||||||
|
return len(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestReadIntErr ensures that errors encountered during reads are propagated
|
||||||
|
// correctly.
|
||||||
|
func TestReadIntErr(t *testing.T) {
|
||||||
|
check := func(name string, size int, fn func(bin byteio.Reader) error) {
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
err := fn(byteio.NewReader(&AbortReader{when: i}))
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
if i < size {
|
||||||
|
t.Errorf("%s: expected err after %d "+
|
||||||
|
"bytes", name, i)
|
||||||
|
}
|
||||||
|
case ErrAbortReader:
|
||||||
|
if i == size {
|
||||||
|
t.Errorf("%s: unexpected err after %d "+
|
||||||
|
"bytes", name, i)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Errorf("%s: unexpected err %v", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check("ReadUint16BE", 2, func(bin byteio.Reader) error { _, err := byteio.ReadUint16BE(bin); return err })
|
||||||
|
check("ReadUint32BE", 4, func(bin byteio.Reader) error { _, err := byteio.ReadUint32BE(bin); return err })
|
||||||
|
check("ReadUint64BE", 8, func(bin byteio.Reader) error { _, err := byteio.ReadUint64BE(bin); return err })
|
||||||
|
check("ReadUint16LE", 2, func(bin byteio.Reader) error { _, err := byteio.ReadUint16LE(bin); return err })
|
||||||
|
check("ReadUint32LE", 4, func(bin byteio.Reader) error { _, err := byteio.ReadUint32LE(bin); return err })
|
||||||
|
check("ReadUint64LE", 8, func(bin byteio.Reader) error { _, err := byteio.ReadUint64LE(bin); return err })
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkReadUint32BE is a simple benchmark for reading 32-bit integers.
|
||||||
|
func BenchmarkReadUint32BE(b *testing.B) {
|
||||||
|
bin := byteio.NewReader(new(MockReader))
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = byteio.ReadUint32BE(bin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkReadUint32BE is a simple benchmark for reading 64-bit integers.
|
||||||
|
func BenchmarkReadUint64BE(b *testing.B) {
|
||||||
|
bin := byteio.NewReader(new(MockReader))
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = byteio.ReadUint64BE(bin)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
package byteio
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
// WriteUint16BE writes an unsigned uint16 in big-endian (network) byte order.
|
||||||
|
func WriteUint16BE(bout Writer, n uint16) error {
|
||||||
|
if err := bout.WriteByte(byte(n >> 8)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return bout.WriteByte(byte(n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteInt16BE writes a signed int16 in big-endian (network) byte order.
|
||||||
|
func WriteInt16BE(bout Writer, i int16) error {
|
||||||
|
return WriteUint16BE(bout, uint16(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteUint32BE writes an unsigned uint32 in big-endian (network) byte order.
|
||||||
|
func WriteUint32BE(bout Writer, n uint32) error {
|
||||||
|
if err := bout.WriteByte(byte(n >> 24)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := bout.WriteByte(byte(n >> 16)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := bout.WriteByte(byte(n >> 8)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return bout.WriteByte(byte(n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteInt32BE writes a signed int32 in big-endian byte order.
|
||||||
|
func WriteInt32BE(bout Writer, i int32) error {
|
||||||
|
return WriteUint32BE(bout, uint32(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFloat32BE writes an IEEE-754 32-bit floating point number in big-endian
|
||||||
|
// (network) byte order.
|
||||||
|
func WriteFloat32BE(bout Writer, f float32) error {
|
||||||
|
return WriteUint32BE(bout, math.Float32bits(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteUint64BE writes an unsigned uint64 in big-endian (network) byte order.
|
||||||
|
func WriteUint64BE(bout Writer, n uint64) error {
|
||||||
|
if err := bout.WriteByte(byte(n >> 56)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := bout.WriteByte(byte(n >> 48)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := bout.WriteByte(byte(n >> 40)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := bout.WriteByte(byte(n >> 32)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := bout.WriteByte(byte(n >> 24)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := bout.WriteByte(byte(n >> 16)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := bout.WriteByte(byte(n >> 8)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return bout.WriteByte(byte(n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteUint64BE writes a signed int64 in big-endian (network) byte order.
|
||||||
|
func WriteInt64BE(bout Writer, i int64) error {
|
||||||
|
return WriteUint64BE(bout, uint64(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFloat32BE writes an IEEE-754 64-bit floating point number in big-endian
|
||||||
|
// (network) byte order.
|
||||||
|
func WriteFloat64BE(bout Writer, f float64) error {
|
||||||
|
return WriteUint64BE(bout, math.Float64bits(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteUint16LE writes an unsigned uint16 in little-endian byte order.
|
||||||
|
func WriteUint16LE(bout Writer, n uint16) error {
|
||||||
|
if err := bout.WriteByte(byte(n)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return bout.WriteByte(byte(n >> 8))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteInt16LE writes a signed int16 in little-endian byte order.
|
||||||
|
func WriteInt16LE(bout Writer, i int16) error {
|
||||||
|
return WriteUint16LE(bout, uint16(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteUint32LE writes an unsigned uint32 in little-endian byte order.
|
||||||
|
func WriteUint32LE(bout Writer, n uint32) error {
|
||||||
|
if err := bout.WriteByte(byte(n)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := bout.WriteByte(byte(n >> 8)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := bout.WriteByte(byte(n >> 16)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return bout.WriteByte(byte(n >> 24))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteInt32LE writes a signed int32 in little-endian byte order.
|
||||||
|
func WriteInt32LE(bout Writer, i int32) error {
|
||||||
|
return WriteUint32LE(bout, uint32(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFloat32LE writes an IEEE-754 32-bit floating point number in
|
||||||
|
// little-endian byte order.
|
||||||
|
func WriteFloat32LE(bout Writer, f float32) error {
|
||||||
|
return WriteUint32LE(bout, math.Float32bits(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteUint64LE writes an unsigned uint64 in little-endian byte order.
|
||||||
|
func WriteUint64LE(bout Writer, n uint64) error {
|
||||||
|
if err := bout.WriteByte(byte(n)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := bout.WriteByte(byte(n >> 8)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := bout.WriteByte(byte(n >> 16)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := bout.WriteByte(byte(n >> 24)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := bout.WriteByte(byte(n >> 32)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := bout.WriteByte(byte(n >> 40)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := bout.WriteByte(byte(n >> 48)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return bout.WriteByte(byte(n >> 56))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteInt64LE writes a signed int64 in little-endian byte order.
|
||||||
|
func WriteInt64LE(bout Writer, i int64) error {
|
||||||
|
return WriteUint64LE(bout, uint64(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFloat64LE writes an IEEE-754 64-bit floating point number in
|
||||||
|
// little-endian byte order.
|
||||||
|
func WriteFloat64LE(bout Writer, f float64) error {
|
||||||
|
return WriteUint64LE(bout, math.Float64bits(f))
|
||||||
|
}
|
|
@ -0,0 +1,260 @@
|
||||||
|
package byteio_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"src.lwithers.me.uk/go/byteio"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestWriteUint ensures correct operation of the WriteUint variants by
|
||||||
|
// verifying written data integrity against encoding/binary.
|
||||||
|
func TestWriteUint(t *testing.T) {
|
||||||
|
checkErr := func(err error) {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected I/O error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare a buffer of test data
|
||||||
|
vals := []uint64{0, 1, 0x8000, 0xFFFF, 0x81828384858687}
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
for _, val := range vals {
|
||||||
|
checkErr(byteio.WriteUint16BE(buf, uint16(val)))
|
||||||
|
checkErr(byteio.WriteUint32BE(buf, uint32(val)))
|
||||||
|
checkErr(byteio.WriteUint64BE(buf, val))
|
||||||
|
checkErr(byteio.WriteUint16LE(buf, uint16(val)))
|
||||||
|
checkErr(byteio.WriteUint32LE(buf, uint32(val)))
|
||||||
|
checkErr(byteio.WriteUint64LE(buf, val))
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify its integrity
|
||||||
|
for _, exp64 := range vals {
|
||||||
|
var (
|
||||||
|
act16, exp16 uint16 = 0, uint16(exp64)
|
||||||
|
act32, exp32 uint32 = 0, uint32(exp64)
|
||||||
|
act64 uint64
|
||||||
|
)
|
||||||
|
|
||||||
|
checkErr(binary.Read(buf, binary.BigEndian, &act16))
|
||||||
|
if act16 != exp16 {
|
||||||
|
t.Errorf("WriteUint16BE: act %X ≠ exp %X", act16, exp16)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkErr(binary.Read(buf, binary.BigEndian, &act32))
|
||||||
|
if act32 != exp32 {
|
||||||
|
t.Errorf("WriteUint32BE: act %X ≠ exp %X", act32, exp32)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkErr(binary.Read(buf, binary.BigEndian, &act64))
|
||||||
|
if act64 != exp64 {
|
||||||
|
t.Errorf("WriteUint64BE: act %X ≠ exp %X", act64, exp64)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkErr(binary.Read(buf, binary.LittleEndian, &act16))
|
||||||
|
if act16 != exp16 {
|
||||||
|
t.Errorf("WriteUint16LE: act %X ≠ exp %X", act16, exp16)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkErr(binary.Read(buf, binary.LittleEndian, &act32))
|
||||||
|
if act32 != exp32 {
|
||||||
|
t.Errorf("WriteUint32LE: act %X ≠ exp %X", act32, exp32)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkErr(binary.Read(buf, binary.LittleEndian, &act64))
|
||||||
|
if act64 != exp64 {
|
||||||
|
t.Errorf("WriteUint64LE: act %X ≠ exp %X", act64, exp64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestWriteInt ensures correct operation of the WriteInt variants by verifying
|
||||||
|
// written data integrity against encoding/binary.
|
||||||
|
func TestWriteInt(t *testing.T) {
|
||||||
|
checkErr := func(err error) {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected I/O error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare a buffer of test data
|
||||||
|
vals := []int64{-1, 0, 1, -0x8000, 0xFFFF, 0x01020304050607}
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
for _, val := range vals {
|
||||||
|
checkErr(byteio.WriteInt16BE(buf, int16(val)))
|
||||||
|
checkErr(byteio.WriteInt32BE(buf, int32(val)))
|
||||||
|
checkErr(byteio.WriteInt64BE(buf, val))
|
||||||
|
checkErr(byteio.WriteInt16LE(buf, int16(val)))
|
||||||
|
checkErr(byteio.WriteInt32LE(buf, int32(val)))
|
||||||
|
checkErr(byteio.WriteInt64LE(buf, val))
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify its integrity
|
||||||
|
for _, exp64 := range vals {
|
||||||
|
var (
|
||||||
|
act16, exp16 int16 = 0, int16(exp64)
|
||||||
|
act32, exp32 int32 = 0, int32(exp64)
|
||||||
|
act64 int64
|
||||||
|
)
|
||||||
|
|
||||||
|
checkErr(binary.Read(buf, binary.BigEndian, &act16))
|
||||||
|
if act16 != exp16 {
|
||||||
|
t.Errorf("WriteInt16BE: act %X ≠ exp %X", act16, exp16)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkErr(binary.Read(buf, binary.BigEndian, &act32))
|
||||||
|
if act32 != exp32 {
|
||||||
|
t.Errorf("WriteInt32BE: act %X ≠ exp %X", act32, exp32)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkErr(binary.Read(buf, binary.BigEndian, &act64))
|
||||||
|
if act64 != exp64 {
|
||||||
|
t.Errorf("WriteInt64BE: act %X ≠ exp %X", act64, exp64)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkErr(binary.Read(buf, binary.LittleEndian, &act16))
|
||||||
|
if act16 != exp16 {
|
||||||
|
t.Errorf("WriteInt16LE: act %X ≠ exp %X", act16, exp16)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkErr(binary.Read(buf, binary.LittleEndian, &act32))
|
||||||
|
if act32 != exp32 {
|
||||||
|
t.Errorf("WriteInt32LE: act %X ≠ exp %X", act32, exp32)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkErr(binary.Read(buf, binary.LittleEndian, &act64))
|
||||||
|
if act64 != exp64 {
|
||||||
|
t.Errorf("WriteInt64LE: act %X ≠ exp %X", act64, exp64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestWriteFloat verifies that byteio-written floating point numbers can be
|
||||||
|
// read back correctly using encoding/binary.
|
||||||
|
func TestWriteFloat(t *testing.T) {
|
||||||
|
vals := []float64{
|
||||||
|
0, 1, -1,
|
||||||
|
math.MaxFloat32, math.SmallestNonzeroFloat32,
|
||||||
|
math.Inf(1), math.Inf(-1), math.NaN(),
|
||||||
|
}
|
||||||
|
checkErr := func(err error) {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected I/O error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare buffer of encoded data
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
for _, val := range vals {
|
||||||
|
checkErr(byteio.WriteFloat32BE(buf, float32(val)))
|
||||||
|
checkErr(byteio.WriteFloat32LE(buf, float32(val)))
|
||||||
|
checkErr(byteio.WriteFloat64BE(buf, val))
|
||||||
|
checkErr(byteio.WriteFloat64LE(buf, val))
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify integrity using encoding/binary
|
||||||
|
for _, exp64 := range vals {
|
||||||
|
var (
|
||||||
|
act32, exp32 float32 = 0, float32(exp64)
|
||||||
|
act64 float64
|
||||||
|
)
|
||||||
|
|
||||||
|
checkErr(binary.Read(buf, binary.BigEndian, &act32))
|
||||||
|
if act32 != exp32 && !math.IsNaN(float64(act32)) && !math.IsNaN(exp64) {
|
||||||
|
t.Errorf("act %f (0x%X) ≠ exp %f (0x%X)",
|
||||||
|
act32, math.Float32bits(act32),
|
||||||
|
exp32, math.Float32bits(exp32))
|
||||||
|
}
|
||||||
|
checkErr(binary.Read(buf, binary.LittleEndian, &act32))
|
||||||
|
if act32 != exp32 && !math.IsNaN(float64(act32)) && !math.IsNaN(exp64) {
|
||||||
|
t.Errorf("act %f (0x%X) ≠ exp %f (0x%X)",
|
||||||
|
act32, math.Float32bits(act32),
|
||||||
|
exp32, math.Float32bits(exp32))
|
||||||
|
}
|
||||||
|
|
||||||
|
checkErr(binary.Read(buf, binary.BigEndian, &act64))
|
||||||
|
if act64 != exp64 && !math.IsNaN(act64) && !math.IsNaN(exp64) {
|
||||||
|
t.Errorf("act %f (0x%X) ≠ exp %f (0x%X)",
|
||||||
|
act64, math.Float64bits(act64),
|
||||||
|
exp64, math.Float64bits(exp64))
|
||||||
|
}
|
||||||
|
checkErr(binary.Read(buf, binary.LittleEndian, &act64))
|
||||||
|
if act64 != exp64 && !math.IsNaN(act64) && !math.IsNaN(exp64) {
|
||||||
|
t.Errorf("act %f (0x%X) ≠ exp %f (0x%X)",
|
||||||
|
act64, math.Float64bits(act64),
|
||||||
|
exp64, math.Float64bits(exp64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrAbortWriter is returned by AbortWriter when the limit is reached.
|
||||||
|
var ErrAbortWriter = errors.New("aborted write")
|
||||||
|
|
||||||
|
// AbortWriter will abort after a certain number of bytes have been written. It
|
||||||
|
// implements byteio.Writer since we do not want it to be wrapped by a
|
||||||
|
// bufio.Writer, which would defer errors until a flush operation.
|
||||||
|
type AbortWriter struct {
|
||||||
|
when, cur int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aw *AbortWriter) WriteByte(b byte) error {
|
||||||
|
if aw.when == aw.cur {
|
||||||
|
return ErrAbortWriter
|
||||||
|
}
|
||||||
|
aw.cur++
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aw *AbortWriter) WriteRune(r rune) (int, error) {
|
||||||
|
len := utf8.RuneLen(r)
|
||||||
|
for i := 0; i < len; i++ {
|
||||||
|
if err := aw.WriteByte(' '); err != nil {
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aw *AbortWriter) Write(buf []byte) (int, error) {
|
||||||
|
for i, b := range buf {
|
||||||
|
if err := aw.WriteByte(b); err != nil {
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestWriteIntErr ensures that an error is correctly returned when writing an
|
||||||
|
// integer and the underlying writer reports an error.
|
||||||
|
func TestWriteIntErr(t *testing.T) {
|
||||||
|
check := func(name string, size int, fn func(bout byteio.Writer) error) {
|
||||||
|
for i := 0; i <= size; i++ {
|
||||||
|
err := fn(&AbortWriter{when: i})
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
if i < size {
|
||||||
|
t.Errorf("%s: expected err after %d "+
|
||||||
|
"bytes", name, i)
|
||||||
|
}
|
||||||
|
case ErrAbortWriter:
|
||||||
|
if i == size {
|
||||||
|
t.Errorf("%s: unexpected err after %d "+
|
||||||
|
"bytes", name, i)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Errorf("%s: unexpected err %v", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check("WriteUint16BE", 2, func(bout byteio.Writer) error { return byteio.WriteUint16BE(bout, 0) })
|
||||||
|
check("WriteUint32BE", 4, func(bout byteio.Writer) error { return byteio.WriteUint32BE(bout, 0) })
|
||||||
|
check("WriteUint64BE", 8, func(bout byteio.Writer) error { return byteio.WriteUint64BE(bout, 0) })
|
||||||
|
check("WriteUint16LE", 2, func(bout byteio.Writer) error { return byteio.WriteUint16LE(bout, 0) })
|
||||||
|
check("WriteUint32LE", 4, func(bout byteio.Writer) error { return byteio.WriteUint32LE(bout, 0) })
|
||||||
|
check("WriteUint64LE", 8, func(bout byteio.Writer) error { return byteio.WriteUint64LE(bout, 0) })
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package byteio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Writer provides byte-oriented writing routines. It is satisfied by
|
||||||
|
// bufio.Writer and bytes.Buffer.
|
||||||
|
type Writer interface {
|
||||||
|
io.Writer
|
||||||
|
io.ByteWriter
|
||||||
|
WriteRune(r rune) (n int, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriter adapts any io.Writer into a byteio.Writer, possibly returning
|
||||||
|
// a new bufio.Writer.
|
||||||
|
func NewWriter(out io.Writer) Writer {
|
||||||
|
if bout, ok := out.(Writer); ok {
|
||||||
|
return bout
|
||||||
|
}
|
||||||
|
return bufio.NewWriter(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
type flusher interface {
|
||||||
|
Flush() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlushIfNecessary tests whether out has a Flush method and if so calls it. If
|
||||||
|
// out does not have a Flush method this function silently does nothing.
|
||||||
|
func FlushIfNecessary(out io.Writer) error {
|
||||||
|
if fout, ok := out.(flusher); ok {
|
||||||
|
return fout.Flush()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package byteio_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"src.lwithers.me.uk/go/byteio"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockWriter discards data sent to it. It also implements flusher.
|
||||||
|
type MockWriter struct {
|
||||||
|
sawFlush bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *MockWriter) Write(buf []byte) (n int, err error) {
|
||||||
|
w.sawFlush = false
|
||||||
|
return len(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *MockWriter) Flush() error {
|
||||||
|
w.sawFlush = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewWriterBytesB checks that a bytes.Buffer is successfully transformed
|
||||||
|
// into a Writer.
|
||||||
|
func TestNewWriterBytesB(t *testing.T) {
|
||||||
|
orig := bytes.NewBuffer(nil)
|
||||||
|
bin := byteio.NewWriter(orig)
|
||||||
|
if act, ok := bin.(*bytes.Buffer); !ok {
|
||||||
|
t.Errorf("Writer(%T) returned unexpected %T", orig, bin)
|
||||||
|
} else if act != orig {
|
||||||
|
t.Errorf("Writer(%p) returned unexpected %p", orig, act)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewWriterBufio checks that a bufio.Writer is successfully transformed
|
||||||
|
// into a Writer.
|
||||||
|
func TestNewWriterBufio(t *testing.T) {
|
||||||
|
orig := bufio.NewWriter(os.Stdin)
|
||||||
|
bin := byteio.NewWriter(orig)
|
||||||
|
if act, ok := bin.(*bufio.Writer); !ok {
|
||||||
|
t.Errorf("Writer(%T) returned unexpected %T", orig, bin)
|
||||||
|
} else if act != orig {
|
||||||
|
t.Errorf("Writer(%p) returned unexpected %p", orig, act)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewWriter checks that an arbitrary io.Writer is successfully wrapped into
|
||||||
|
// a byteio.Writer.
|
||||||
|
func TestNewWriter(t *testing.T) {
|
||||||
|
orig := new(MockWriter)
|
||||||
|
bin := byteio.NewWriter(orig)
|
||||||
|
if _, ok := bin.(*bufio.Writer); !ok {
|
||||||
|
t.Errorf("Writer(%T) did not wrap to bufio.Writer (got %T)",
|
||||||
|
orig, bin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFlushIfNecessary checks that no error is returned when called on
|
||||||
|
// something which does not require flush, and that Flush() is indeed called
|
||||||
|
// if it is presnet.
|
||||||
|
func TestFlushIfNecessary(t *testing.T) {
|
||||||
|
noflush := bytes.NewBuffer(nil)
|
||||||
|
if err := byteio.FlushIfNecessary(noflush); err != nil {
|
||||||
|
t.Errorf("unexpected error while not flushing: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w := new(MockWriter)
|
||||||
|
if err := byteio.FlushIfNecessary(w); err != nil {
|
||||||
|
t.Errorf("unexpected error while flushing: %v", err)
|
||||||
|
}
|
||||||
|
if !w.sawFlush {
|
||||||
|
t.Error("Flush() was not called")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue