| package cbor |
| |
| // This file contains code to decode a stream of CBOR Data into JSON. |
| |
| import ( |
| "bufio" |
| "bytes" |
| "fmt" |
| "io" |
| "math" |
| "net" |
| "runtime" |
| "strconv" |
| "strings" |
| "time" |
| "unicode/utf8" |
| ) |
| |
| var decodeTimeZone *time.Location |
| |
| const hexTable = "0123456789abcdef" |
| |
| const isFloat32 = 4 |
| const isFloat64 = 8 |
| |
| func readNBytes(src *bufio.Reader, n int) []byte { |
| ret := make([]byte, n) |
| for i := 0; i < n; i++ { |
| ch, e := src.ReadByte() |
| if e != nil { |
| panic(fmt.Errorf("Tried to Read %d Bytes.. But hit end of file", n)) |
| } |
| ret[i] = ch |
| } |
| return ret |
| } |
| |
| func readByte(src *bufio.Reader) byte { |
| b, e := src.ReadByte() |
| if e != nil { |
| panic(fmt.Errorf("Tried to Read 1 Byte.. But hit end of file")) |
| } |
| return b |
| } |
| |
| func decodeIntAdditonalType(src *bufio.Reader, minor byte) int64 { |
| val := int64(0) |
| if minor <= 23 { |
| val = int64(minor) |
| } else { |
| bytesToRead := 0 |
| switch minor { |
| case additionalTypeIntUint8: |
| bytesToRead = 1 |
| case additionalTypeIntUint16: |
| bytesToRead = 2 |
| case additionalTypeIntUint32: |
| bytesToRead = 4 |
| case additionalTypeIntUint64: |
| bytesToRead = 8 |
| default: |
| panic(fmt.Errorf("Invalid Additional Type: %d in decodeInteger (expected <28)", minor)) |
| } |
| pb := readNBytes(src, bytesToRead) |
| for i := 0; i < bytesToRead; i++ { |
| val = val * 256 |
| val += int64(pb[i]) |
| } |
| } |
| return val |
| } |
| |
| func decodeInteger(src *bufio.Reader) int64 { |
| pb := readByte(src) |
| major := pb & maskOutAdditionalType |
| minor := pb & maskOutMajorType |
| if major != majorTypeUnsignedInt && major != majorTypeNegativeInt { |
| panic(fmt.Errorf("Major type is: %d in decodeInteger!! (expected 0 or 1)", major)) |
| } |
| val := decodeIntAdditonalType(src, minor) |
| if major == 0 { |
| return val |
| } |
| return (-1 - val) |
| } |
| |
| func decodeFloat(src *bufio.Reader) (float64, int) { |
| pb := readByte(src) |
| major := pb & maskOutAdditionalType |
| minor := pb & maskOutMajorType |
| if major != majorTypeSimpleAndFloat { |
| panic(fmt.Errorf("Incorrect Major type is: %d in decodeFloat", major)) |
| } |
| |
| switch minor { |
| case additionalTypeFloat16: |
| panic(fmt.Errorf("float16 is not suppported in decodeFloat")) |
| |
| case additionalTypeFloat32: |
| pb := readNBytes(src, 4) |
| switch string(pb) { |
| case float32Nan: |
| return math.NaN(), isFloat32 |
| case float32PosInfinity: |
| return math.Inf(0), isFloat32 |
| case float32NegInfinity: |
| return math.Inf(-1), isFloat32 |
| } |
| n := uint32(0) |
| for i := 0; i < 4; i++ { |
| n = n * 256 |
| n += uint32(pb[i]) |
| } |
| val := math.Float32frombits(n) |
| return float64(val), isFloat32 |
| case additionalTypeFloat64: |
| pb := readNBytes(src, 8) |
| switch string(pb) { |
| case float64Nan: |
| return math.NaN(), isFloat64 |
| case float64PosInfinity: |
| return math.Inf(0), isFloat64 |
| case float64NegInfinity: |
| return math.Inf(-1), isFloat64 |
| } |
| n := uint64(0) |
| for i := 0; i < 8; i++ { |
| n = n * 256 |
| n += uint64(pb[i]) |
| } |
| val := math.Float64frombits(n) |
| return val, isFloat64 |
| } |
| panic(fmt.Errorf("Invalid Additional Type: %d in decodeFloat", minor)) |
| } |
| |
| func decodeStringComplex(dst []byte, s string, pos uint) []byte { |
| i := int(pos) |
| start := 0 |
| |
| for i < len(s) { |
| b := s[i] |
| if b >= utf8.RuneSelf { |
| r, size := utf8.DecodeRuneInString(s[i:]) |
| if r == utf8.RuneError && size == 1 { |
| // In case of error, first append previous simple characters to |
| // the byte slice if any and append a replacement character code |
| // in place of the invalid sequence. |
| if start < i { |
| dst = append(dst, s[start:i]...) |
| } |
| dst = append(dst, `\ufffd`...) |
| i += size |
| start = i |
| continue |
| } |
| i += size |
| continue |
| } |
| if b >= 0x20 && b <= 0x7e && b != '\\' && b != '"' { |
| i++ |
| continue |
| } |
| // We encountered a character that needs to be encoded. |
| // Let's append the previous simple characters to the byte slice |
| // and switch our operation to read and encode the remainder |
| // characters byte-by-byte. |
| if start < i { |
| dst = append(dst, s[start:i]...) |
| } |
| switch b { |
| case '"', '\\': |
| dst = append(dst, '\\', b) |
| case '\b': |
| dst = append(dst, '\\', 'b') |
| case '\f': |
| dst = append(dst, '\\', 'f') |
| case '\n': |
| dst = append(dst, '\\', 'n') |
| case '\r': |
| dst = append(dst, '\\', 'r') |
| case '\t': |
| dst = append(dst, '\\', 't') |
| default: |
| dst = append(dst, '\\', 'u', '0', '0', hexTable[b>>4], hexTable[b&0xF]) |
| } |
| i++ |
| start = i |
| } |
| if start < len(s) { |
| dst = append(dst, s[start:]...) |
| } |
| return dst |
| } |
| |
| func decodeString(src *bufio.Reader, noQuotes bool) []byte { |
| pb := readByte(src) |
| major := pb & maskOutAdditionalType |
| minor := pb & maskOutMajorType |
| if major != majorTypeByteString { |
| panic(fmt.Errorf("Major type is: %d in decodeString", major)) |
| } |
| result := []byte{} |
| if !noQuotes { |
| result = append(result, '"') |
| } |
| length := decodeIntAdditonalType(src, minor) |
| len := int(length) |
| pbs := readNBytes(src, len) |
| result = append(result, pbs...) |
| if noQuotes { |
| return result |
| } |
| return append(result, '"') |
| } |
| |
| func decodeUTF8String(src *bufio.Reader) []byte { |
| pb := readByte(src) |
| major := pb & maskOutAdditionalType |
| minor := pb & maskOutMajorType |
| if major != majorTypeUtf8String { |
| panic(fmt.Errorf("Major type is: %d in decodeUTF8String", major)) |
| } |
| result := []byte{'"'} |
| length := decodeIntAdditonalType(src, minor) |
| len := int(length) |
| pbs := readNBytes(src, len) |
| |
| for i := 0; i < len; i++ { |
| // Check if the character needs encoding. Control characters, slashes, |
| // and the double quote need json encoding. Bytes above the ascii |
| // boundary needs utf8 encoding. |
| if pbs[i] < 0x20 || pbs[i] > 0x7e || pbs[i] == '\\' || pbs[i] == '"' { |
| // We encountered a character that needs to be encoded. Switch |
| // to complex version of the algorithm. |
| dst := []byte{'"'} |
| dst = decodeStringComplex(dst, string(pbs), uint(i)) |
| return append(dst, '"') |
| } |
| } |
| // The string has no need for encoding an therefore is directly |
| // appended to the byte slice. |
| result = append(result, pbs...) |
| return append(result, '"') |
| } |
| |
| func array2Json(src *bufio.Reader, dst io.Writer) { |
| dst.Write([]byte{'['}) |
| pb := readByte(src) |
| major := pb & maskOutAdditionalType |
| minor := pb & maskOutMajorType |
| if major != majorTypeArray { |
| panic(fmt.Errorf("Major type is: %d in array2Json", major)) |
| } |
| len := 0 |
| unSpecifiedCount := false |
| if minor == additionalTypeInfiniteCount { |
| unSpecifiedCount = true |
| } else { |
| length := decodeIntAdditonalType(src, minor) |
| len = int(length) |
| } |
| for i := 0; unSpecifiedCount || i < len; i++ { |
| if unSpecifiedCount { |
| pb, e := src.Peek(1) |
| if e != nil { |
| panic(e) |
| } |
| if pb[0] == byte(majorTypeSimpleAndFloat|additionalTypeBreak) { |
| readByte(src) |
| break |
| } |
| } |
| cbor2JsonOneObject(src, dst) |
| if unSpecifiedCount { |
| pb, e := src.Peek(1) |
| if e != nil { |
| panic(e) |
| } |
| if pb[0] == byte(majorTypeSimpleAndFloat|additionalTypeBreak) { |
| readByte(src) |
| break |
| } |
| dst.Write([]byte{','}) |
| } else if i+1 < len { |
| dst.Write([]byte{','}) |
| } |
| } |
| dst.Write([]byte{']'}) |
| } |
| |
| func map2Json(src *bufio.Reader, dst io.Writer) { |
| pb := readByte(src) |
| major := pb & maskOutAdditionalType |
| minor := pb & maskOutMajorType |
| if major != majorTypeMap { |
| panic(fmt.Errorf("Major type is: %d in map2Json", major)) |
| } |
| len := 0 |
| unSpecifiedCount := false |
| if minor == additionalTypeInfiniteCount { |
| unSpecifiedCount = true |
| } else { |
| length := decodeIntAdditonalType(src, minor) |
| len = int(length) |
| } |
| dst.Write([]byte{'{'}) |
| for i := 0; unSpecifiedCount || i < len; i++ { |
| if unSpecifiedCount { |
| pb, e := src.Peek(1) |
| if e != nil { |
| panic(e) |
| } |
| if pb[0] == byte(majorTypeSimpleAndFloat|additionalTypeBreak) { |
| readByte(src) |
| break |
| } |
| } |
| cbor2JsonOneObject(src, dst) |
| if i%2 == 0 { |
| // Even position values are keys. |
| dst.Write([]byte{':'}) |
| } else { |
| if unSpecifiedCount { |
| pb, e := src.Peek(1) |
| if e != nil { |
| panic(e) |
| } |
| if pb[0] == byte(majorTypeSimpleAndFloat|additionalTypeBreak) { |
| readByte(src) |
| break |
| } |
| dst.Write([]byte{','}) |
| } else if i+1 < len { |
| dst.Write([]byte{','}) |
| } |
| } |
| } |
| dst.Write([]byte{'}'}) |
| } |
| |
| func decodeTagData(src *bufio.Reader) []byte { |
| pb := readByte(src) |
| major := pb & maskOutAdditionalType |
| minor := pb & maskOutMajorType |
| if major != majorTypeTags { |
| panic(fmt.Errorf("Major type is: %d in decodeTagData", major)) |
| } |
| switch minor { |
| case additionalTypeTimestamp: |
| return decodeTimeStamp(src) |
| |
| // Tag value is larger than 256 (so uint16). |
| case additionalTypeIntUint16: |
| val := decodeIntAdditonalType(src, minor) |
| |
| switch uint16(val) { |
| case additionalTypeEmbeddedJSON: |
| pb := readByte(src) |
| dataMajor := pb & maskOutAdditionalType |
| if dataMajor != majorTypeByteString { |
| panic(fmt.Errorf("Unsupported embedded Type: %d in decodeEmbeddedJSON", dataMajor)) |
| } |
| src.UnreadByte() |
| return decodeString(src, true) |
| |
| case additionalTypeTagNetworkAddr: |
| octets := decodeString(src, true) |
| ss := []byte{'"'} |
| switch len(octets) { |
| case 6: // MAC address. |
| ha := net.HardwareAddr(octets) |
| ss = append(append(ss, ha.String()...), '"') |
| case 4: // IPv4 address. |
| fallthrough |
| case 16: // IPv6 address. |
| ip := net.IP(octets) |
| ss = append(append(ss, ip.String()...), '"') |
| default: |
| panic(fmt.Errorf("Unexpected Network Address length: %d (expected 4,6,16)", len(octets))) |
| } |
| return ss |
| |
| case additionalTypeTagNetworkPrefix: |
| pb := readByte(src) |
| if pb != byte(majorTypeMap|0x1) { |
| panic(fmt.Errorf("IP Prefix is NOT of MAP of 1 elements as expected")) |
| } |
| octets := decodeString(src, true) |
| val := decodeInteger(src) |
| ip := net.IP(octets) |
| var mask net.IPMask |
| pfxLen := int(val) |
| if len(octets) == 4 { |
| mask = net.CIDRMask(pfxLen, 32) |
| } else { |
| mask = net.CIDRMask(pfxLen, 128) |
| } |
| ipPfx := net.IPNet{IP: ip, Mask: mask} |
| ss := []byte{'"'} |
| ss = append(append(ss, ipPfx.String()...), '"') |
| return ss |
| |
| case additionalTypeTagHexString: |
| octets := decodeString(src, true) |
| ss := []byte{'"'} |
| for _, v := range octets { |
| ss = append(ss, hexTable[v>>4], hexTable[v&0x0f]) |
| } |
| return append(ss, '"') |
| |
| default: |
| panic(fmt.Errorf("Unsupported Additional Tag Type: %d in decodeTagData", val)) |
| } |
| } |
| panic(fmt.Errorf("Unsupported Additional Type: %d in decodeTagData", minor)) |
| } |
| |
| func decodeTimeStamp(src *bufio.Reader) []byte { |
| pb := readByte(src) |
| src.UnreadByte() |
| tsMajor := pb & maskOutAdditionalType |
| if tsMajor == majorTypeUnsignedInt || tsMajor == majorTypeNegativeInt { |
| n := decodeInteger(src) |
| t := time.Unix(n, 0) |
| if decodeTimeZone != nil { |
| t = t.In(decodeTimeZone) |
| } else { |
| t = t.In(time.UTC) |
| } |
| tsb := []byte{} |
| tsb = append(tsb, '"') |
| tsb = t.AppendFormat(tsb, IntegerTimeFieldFormat) |
| tsb = append(tsb, '"') |
| return tsb |
| } else if tsMajor == majorTypeSimpleAndFloat { |
| n, _ := decodeFloat(src) |
| secs := int64(n) |
| n -= float64(secs) |
| n *= float64(1e9) |
| t := time.Unix(secs, int64(n)) |
| if decodeTimeZone != nil { |
| t = t.In(decodeTimeZone) |
| } else { |
| t = t.In(time.UTC) |
| } |
| tsb := []byte{} |
| tsb = append(tsb, '"') |
| tsb = t.AppendFormat(tsb, NanoTimeFieldFormat) |
| tsb = append(tsb, '"') |
| return tsb |
| } |
| panic(fmt.Errorf("TS format is neigther int nor float: %d", tsMajor)) |
| } |
| |
| func decodeSimpleFloat(src *bufio.Reader) []byte { |
| pb := readByte(src) |
| major := pb & maskOutAdditionalType |
| minor := pb & maskOutMajorType |
| if major != majorTypeSimpleAndFloat { |
| panic(fmt.Errorf("Major type is: %d in decodeSimpleFloat", major)) |
| } |
| switch minor { |
| case additionalTypeBoolTrue: |
| return []byte("true") |
| case additionalTypeBoolFalse: |
| return []byte("false") |
| case additionalTypeNull: |
| return []byte("null") |
| case additionalTypeFloat16: |
| fallthrough |
| case additionalTypeFloat32: |
| fallthrough |
| case additionalTypeFloat64: |
| src.UnreadByte() |
| v, bc := decodeFloat(src) |
| ba := []byte{} |
| switch { |
| case math.IsNaN(v): |
| return []byte("\"NaN\"") |
| case math.IsInf(v, 1): |
| return []byte("\"+Inf\"") |
| case math.IsInf(v, -1): |
| return []byte("\"-Inf\"") |
| } |
| if bc == isFloat32 { |
| ba = strconv.AppendFloat(ba, v, 'f', -1, 32) |
| } else if bc == isFloat64 { |
| ba = strconv.AppendFloat(ba, v, 'f', -1, 64) |
| } else { |
| panic(fmt.Errorf("Invalid Float precision from decodeFloat: %d", bc)) |
| } |
| return ba |
| default: |
| panic(fmt.Errorf("Invalid Additional Type: %d in decodeSimpleFloat", minor)) |
| } |
| } |
| |
| func cbor2JsonOneObject(src *bufio.Reader, dst io.Writer) { |
| pb, e := src.Peek(1) |
| if e != nil { |
| panic(e) |
| } |
| major := (pb[0] & maskOutAdditionalType) |
| |
| switch major { |
| case majorTypeUnsignedInt: |
| fallthrough |
| case majorTypeNegativeInt: |
| n := decodeInteger(src) |
| dst.Write([]byte(strconv.Itoa(int(n)))) |
| |
| case majorTypeByteString: |
| s := decodeString(src, false) |
| dst.Write(s) |
| |
| case majorTypeUtf8String: |
| s := decodeUTF8String(src) |
| dst.Write(s) |
| |
| case majorTypeArray: |
| array2Json(src, dst) |
| |
| case majorTypeMap: |
| map2Json(src, dst) |
| |
| case majorTypeTags: |
| s := decodeTagData(src) |
| dst.Write(s) |
| |
| case majorTypeSimpleAndFloat: |
| s := decodeSimpleFloat(src) |
| dst.Write(s) |
| } |
| } |
| |
| func moreBytesToRead(src *bufio.Reader) bool { |
| _, e := src.ReadByte() |
| if e == nil { |
| src.UnreadByte() |
| return true |
| } |
| return false |
| } |
| |
| // Cbor2JsonManyObjects decodes all the CBOR Objects read from src |
| // reader. It keeps on decoding until reader returns EOF (error when reading). |
| // Decoded string is written to the dst. At the end of every CBOR Object |
| // newline is written to the output stream. |
| // |
| // Returns error (if any) that was encountered during decode. |
| // The child functions will generate a panic when error is encountered and |
| // this function will recover non-runtime Errors and return the reason as error. |
| func Cbor2JsonManyObjects(src io.Reader, dst io.Writer) (err error) { |
| defer func() { |
| if r := recover(); r != nil { |
| if _, ok := r.(runtime.Error); ok { |
| panic(r) |
| } |
| err = r.(error) |
| } |
| }() |
| bufRdr := bufio.NewReader(src) |
| for moreBytesToRead(bufRdr) { |
| cbor2JsonOneObject(bufRdr, dst) |
| dst.Write([]byte("\n")) |
| } |
| return nil |
| } |
| |
| // Detect if the bytes to be printed is Binary or not. |
| func binaryFmt(p []byte) bool { |
| if len(p) > 0 && p[0] > 0x7F { |
| return true |
| } |
| return false |
| } |
| |
| func getReader(str string) *bufio.Reader { |
| return bufio.NewReader(strings.NewReader(str)) |
| } |
| |
| // DecodeIfBinaryToString converts a binary formatted log msg to a |
| // JSON formatted String Log message - suitable for printing to Console/Syslog. |
| func DecodeIfBinaryToString(in []byte) string { |
| if binaryFmt(in) { |
| var b bytes.Buffer |
| Cbor2JsonManyObjects(strings.NewReader(string(in)), &b) |
| return b.String() |
| } |
| return string(in) |
| } |
| |
| // DecodeObjectToStr checks if the input is a binary format, if so, |
| // it will decode a single Object and return the decoded string. |
| func DecodeObjectToStr(in []byte) string { |
| if binaryFmt(in) { |
| var b bytes.Buffer |
| cbor2JsonOneObject(getReader(string(in)), &b) |
| return b.String() |
| } |
| return string(in) |
| } |
| |
| // DecodeIfBinaryToBytes checks if the input is a binary format, if so, |
| // it will decode all Objects and return the decoded string as byte array. |
| func DecodeIfBinaryToBytes(in []byte) []byte { |
| if binaryFmt(in) { |
| var b bytes.Buffer |
| Cbor2JsonManyObjects(bytes.NewReader(in), &b) |
| return b.Bytes() |
| } |
| return in |
| } |