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