| // Copyright 2009 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package hex implements hexadecimal encoding and decoding. |
| package hex |
| |
| import ( |
| "bytes" |
| "errors" |
| "fmt" |
| "io" |
| ) |
| |
| var hextable = [16]byte{ |
| '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', |
| 'a', 'b', 'c', 'd', 'e', 'f', |
| } |
| |
| // EncodedLen returns the length of an encoding of n source bytes. |
| // Specifically, it returns n * 2. |
| func EncodedLen(n int) int { return n * 2 } |
| |
| // Encode encodes src into EncodedLen(len(src)) |
| // bytes of dst. As a convenience, it returns the number |
| // of bytes written to dst, but this value is always EncodedLen(len(src)). |
| // Encode implements hexadecimal encoding. |
| func Encode(dst, src []byte) int { |
| for i, v := range src { |
| dst[i*2] = hextable[v>>4] |
| dst[i*2+1] = hextable[v&0x0f] |
| } |
| |
| return len(src) * 2 |
| } |
| |
| // ErrLength results from decoding an odd length slice. |
| var ErrLength = errors.New("encoding/hex: odd length hex string") |
| |
| // InvalidByteError values describe errors resulting from an invalid byte in a hex string. |
| type InvalidByteError byte |
| |
| func (e InvalidByteError) Error() string { |
| return fmt.Sprintf("encoding/hex: invalid byte: %#U", rune(e)) |
| } |
| |
| // DecodedLen returns the length of a decoding of x source bytes. |
| // Specifically, it returns x / 2. |
| func DecodedLen(x int) int { return x / 2 } |
| |
| // Decode decodes src into DecodedLen(len(src)) bytes, |
| // returning the actual number of bytes written to dst. |
| // |
| // Decode expects that src contain only hexadecimal |
| // characters and that src should have an even length. |
| func Decode(dst, src []byte) (int, error) { |
| if len(src)%2 == 1 { |
| return 0, ErrLength |
| } |
| |
| for i := 0; i < len(src)/2; i++ { |
| a, ok := fromHexChar(src[i*2]) |
| if !ok { |
| return 0, InvalidByteError(src[i*2]) |
| } |
| b, ok := fromHexChar(src[i*2+1]) |
| if !ok { |
| return 0, InvalidByteError(src[i*2+1]) |
| } |
| dst[i] = (a << 4) | b |
| } |
| |
| return len(src) / 2, nil |
| } |
| |
| // fromHexChar converts a hex character into its value and a success flag. |
| func fromHexChar(c byte) (byte, bool) { |
| switch { |
| case '0' <= c && c <= '9': |
| return c - '0', true |
| case 'a' <= c && c <= 'f': |
| return c - 'a' + 10, true |
| case 'A' <= c && c <= 'F': |
| return c - 'A' + 10, true |
| } |
| |
| return 0, false |
| } |
| |
| // EncodeToString returns the hexadecimal encoding of src. |
| func EncodeToString(src []byte) string { |
| dst := make([]byte, EncodedLen(len(src))) |
| Encode(dst, src) |
| return string(dst) |
| } |
| |
| // DecodeString returns the bytes represented by the hexadecimal string s. |
| func DecodeString(s string) ([]byte, error) { |
| src := []byte(s) |
| dst := make([]byte, DecodedLen(len(src))) |
| _, err := Decode(dst, src) |
| if err != nil { |
| return nil, err |
| } |
| return dst, nil |
| } |
| |
| // Dump returns a string that contains a hex dump of the given data. The format |
| // of the hex dump matches the output of `hexdump -C` on the command line. |
| func Dump(data []byte) string { |
| var buf bytes.Buffer |
| dumper := Dumper(&buf) |
| dumper.Write(data) |
| dumper.Close() |
| return buf.String() |
| } |
| |
| // 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. |
| func Dumper(w io.Writer) io.WriteCloser { |
| return &dumper{w: w} |
| } |
| |
| type dumper struct { |
| w io.Writer |
| rightChars [18]byte |
| buf [14]byte |
| used int // number of bytes in the current line |
| n uint // number of bytes, total |
| } |
| |
| func toChar(b byte) byte { |
| if b < 32 || b > 126 { |
| return '.' |
| } |
| return b |
| } |
| |
| func (h *dumper) Write(data []byte) (n int, err error) { |
| // Output lines look like: |
| // 00000010 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d |./0123456789:;<=| |
| // ^ offset ^ extra space ^ ASCII of line. |
| for i := range data { |
| if h.used == 0 { |
| // At the beginning of a line we print the current |
| // offset in hex. |
| h.buf[0] = byte(h.n >> 24) |
| h.buf[1] = byte(h.n >> 16) |
| h.buf[2] = byte(h.n >> 8) |
| h.buf[3] = byte(h.n) |
| Encode(h.buf[4:], h.buf[:4]) |
| h.buf[12] = ' ' |
| h.buf[13] = ' ' |
| _, err = h.w.Write(h.buf[4:]) |
| if err != nil { |
| return |
| } |
| } |
| Encode(h.buf[:], data[i:i+1]) |
| h.buf[2] = ' ' |
| l := 3 |
| if h.used == 7 { |
| // There's an additional space after the 8th byte. |
| h.buf[3] = ' ' |
| l = 4 |
| } else if h.used == 15 { |
| // At the end of the line there's an extra space and |
| // the bar for the right column. |
| h.buf[3] = ' ' |
| h.buf[4] = '|' |
| l = 5 |
| } |
| _, err = h.w.Write(h.buf[:l]) |
| if err != nil { |
| return |
| } |
| n++ |
| h.rightChars[h.used] = toChar(data[i]) |
| h.used++ |
| h.n++ |
| if h.used == 16 { |
| h.rightChars[16] = '|' |
| h.rightChars[17] = '\n' |
| _, err = h.w.Write(h.rightChars[:]) |
| if err != nil { |
| return |
| } |
| h.used = 0 |
| } |
| } |
| return |
| } |
| |
| func (h *dumper) Close() (err error) { |
| // See the comments in Write() for the details of this format. |
| if h.used == 0 { |
| return |
| } |
| h.buf[0] = ' ' |
| h.buf[1] = ' ' |
| h.buf[2] = ' ' |
| h.buf[3] = ' ' |
| h.buf[4] = '|' |
| nBytes := h.used |
| for h.used < 16 { |
| l := 3 |
| if h.used == 7 { |
| l = 4 |
| } else if h.used == 15 { |
| l = 5 |
| } |
| _, err = h.w.Write(h.buf[:l]) |
| if err != nil { |
| return |
| } |
| h.used++ |
| } |
| h.rightChars[nBytes] = '|' |
| h.rightChars[nBytes+1] = '\n' |
| _, err = h.w.Write(h.rightChars[:nBytes+2]) |
| return |
| } |