From 6db4950dc57deb899bf5550411c01ce32f16bdd0 Mon Sep 17 00:00:00 2001 From: Tim Cooper Date: Wed, 11 Oct 2017 22:05:03 -0300 Subject: encoding/hex: add NewEncoder, NewDecoder NewEncoder returns an io.Writer that writes all incoming bytes as hexadecimal characters to the underlying io.Writer. NewDecoder returns an io.Reader that does the inverse. Fixes #21590 Change-Id: Iebe0813faf365b42598f19a9aa41768f571dc0a8 Reviewed-on: https://go-review.googlesource.com/70210 Reviewed-by: Joe Tsai Reviewed-by: Ian Lance Taylor --- src/encoding/hex/hex.go | 75 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 2 deletions(-) (limited to 'src/encoding/hex/hex.go') diff --git a/src/encoding/hex/hex.go b/src/encoding/hex/hex.go index 18e0c09ef3..f47b7fa34e 100644 --- a/src/encoding/hex/hex.go +++ b/src/encoding/hex/hex.go @@ -58,11 +58,11 @@ func Decode(dst, src []byte) (int, error) { for i := 0; i < len(src)/2; i++ { a, ok := fromHexChar(src[i*2]) if !ok { - return 0, InvalidByteError(src[i*2]) + return i, InvalidByteError(src[i*2]) } b, ok := fromHexChar(src[i*2+1]) if !ok { - return 0, InvalidByteError(src[i*2+1]) + return i, InvalidByteError(src[i*2+1]) } dst[i] = (a << 4) | b } @@ -113,6 +113,77 @@ func Dump(data []byte) string { return buf.String() } +// bufferSize is the number of hexadecimal characters to buffer in encoder and decoder. +const bufferSize = 1024 + +type encoder struct { + w io.Writer + err error + out [bufferSize]byte // output buffer +} + +// NewEncoder returns an io.Writer that writes lowercase hexadecimal characters to w. +func NewEncoder(w io.Writer) io.Writer { + return &encoder{w: w} +} + +func (e *encoder) Write(p []byte) (n int, err error) { + for len(p) > 0 && e.err == nil { + chunkSize := bufferSize / 2 + if len(p) < chunkSize { + chunkSize = len(p) + } + + var written int + encoded := Encode(e.out[:], p[:chunkSize]) + written, e.err = e.w.Write(e.out[:encoded]) + n += written / 2 + p = p[chunkSize:] + } + return n, e.err +} + +type decoder struct { + r io.Reader + err error + in []byte // input buffer (encoded form) + arr [bufferSize]byte // backing array for in +} + +// NewDecoder returns an io.Reader that decodes hexadecimal characters from r. +// NewDecoder expects that r contain only an even number of hexadecimal characters. +func NewDecoder(r io.Reader) io.Reader { + return &decoder{r: r} +} + +func (d *decoder) Read(p []byte) (n int, err error) { + // Fill internal buffer with sufficient bytes to decode + if len(d.in) < 2 && d.err == nil { + var numCopy, numRead int + numCopy = copy(d.arr[:], d.in) // Copies either 0 or 1 bytes + numRead, d.err = d.r.Read(d.arr[numCopy:]) + d.in = d.arr[:numCopy+numRead] + if d.err == io.EOF && len(d.in)%2 != 0 { + d.err = io.ErrUnexpectedEOF + } + } + + // Decode internal buffer into output buffer + if numAvail := len(d.in) / 2; len(p) > numAvail { + p = p[:numAvail] + } + numDec, err := Decode(p, d.in[:len(p)*2]) + d.in = d.in[2*numDec:] + if err != nil { + d.in, d.err = nil, err // Decode error; discard input remainder + } + + if len(d.in) < 2 { + return numDec, d.err // Only expose errors when buffer fully consumed + } + return numDec, nil +} + // Dumper returns a WriteCloser that writes a hex dump of all written data to // w. The format of the dump matches the output of `hexdump -C` on the command // line. -- cgit v1.3