github.com/gogo/protobuf@v1.3.2/test/issue411/ids.go (about) 1 package issue411 2 3 import ( 4 "encoding/base64" 5 "encoding/binary" 6 "fmt" 7 "strconv" 8 ) 9 10 // TraceID is a random 128bit identifier for a trace 11 type TraceID struct { 12 Low uint64 `json:"lo"` 13 High uint64 `json:"hi"` 14 } 15 16 // SpanID is a random 64bit identifier for a span 17 type SpanID uint64 18 19 // ------- TraceID ------- 20 21 // NewTraceID creates a new TraceID from two 64bit unsigned ints. 22 func NewTraceID(high, low uint64) TraceID { 23 return TraceID{High: high, Low: low} 24 } 25 26 func (t TraceID) String() string { 27 if t.High == 0 { 28 return fmt.Sprintf("%x", t.Low) 29 } 30 return fmt.Sprintf("%x%016x", t.High, t.Low) 31 } 32 33 // TraceIDFromString creates a TraceID from a hexadecimal string 34 func TraceIDFromString(s string) (TraceID, error) { 35 var hi, lo uint64 36 var err error 37 if len(s) > 32 { 38 return TraceID{}, fmt.Errorf("TraceID cannot be longer than 32 hex characters: %s", s) 39 } else if len(s) > 16 { 40 hiLen := len(s) - 16 41 if hi, err = strconv.ParseUint(s[0:hiLen], 16, 64); err != nil { 42 return TraceID{}, err 43 } 44 if lo, err = strconv.ParseUint(s[hiLen:], 16, 64); err != nil { 45 return TraceID{}, err 46 } 47 } else { 48 if lo, err = strconv.ParseUint(s, 16, 64); err != nil { 49 return TraceID{}, err 50 } 51 } 52 return TraceID{High: hi, Low: lo}, nil 53 } 54 55 // MarshalText is called by encoding/json, which we do not want people to use. 56 func (t TraceID) MarshalText() ([]byte, error) { 57 return nil, fmt.Errorf("unsupported method TraceID.MarshalText; please use github.com/gogo/protobuf/jsonpb for marshalling") 58 } 59 60 // UnmarshalText is called by encoding/json, which we do not want people to use. 61 func (t *TraceID) UnmarshalText(text []byte) error { 62 return fmt.Errorf("unsupported method TraceID.UnmarshalText; please use github.com/gogo/protobuf/jsonpb for marshalling") 63 } 64 65 // Size returns the size of this datum in protobuf. It is always 16 bytes. 66 func (t *TraceID) Size() int { 67 return 16 68 } 69 70 // Marshal converts trace ID into a binary representation. Called by protobuf serialization. 71 func (t TraceID) Marshal() ([]byte, error) { 72 b := make([]byte, t.Size()) 73 _, err := t.MarshalTo(b) 74 return b, err 75 } 76 77 // MarshalTo converts trace ID into a binary representation. Called by protobuf serialization. 78 func (t *TraceID) MarshalTo(data []byte) (n int, err error) { 79 var b [16]byte 80 binary.BigEndian.PutUint64(b[:8], uint64(t.High)) 81 binary.BigEndian.PutUint64(b[8:], uint64(t.Low)) 82 return marshalBytes(data, b[:]) 83 } 84 85 // Unmarshal inflates this trace ID from binary representation. Called by protobuf serialization. 86 func (t *TraceID) Unmarshal(data []byte) error { 87 if len(data) < 16 { 88 return fmt.Errorf("buffer is too short") 89 } 90 t.High = binary.BigEndian.Uint64(data[:8]) 91 t.Low = binary.BigEndian.Uint64(data[8:]) 92 return nil 93 } 94 95 func marshalBytes(dst []byte, src []byte) (n int, err error) { 96 if len(dst) < len(src) { 97 return 0, fmt.Errorf("buffer is too short") 98 } 99 return copy(dst, src), nil 100 } 101 102 // MarshalJSON converts trace id into a base64 string enclosed in quotes. 103 // Used by protobuf JSON serialization. 104 // Example: {high:2, low:1} => "AAAAAAAAAAIAAAAAAAAAAQ==". 105 func (t TraceID) MarshalJSON() ([]byte, error) { 106 var b [16]byte 107 _, err := t.MarshalTo(b[:]) // can only error on incorrect buffer size 108 if err != nil { 109 return []byte{}, err 110 } 111 s := make([]byte, 24+2) 112 base64.StdEncoding.Encode(s[1:25], b[:]) 113 s[0], s[25] = '"', '"' 114 return s, nil 115 } 116 117 // UnmarshalJSON inflates trace id from base64 string, possibly enclosed in quotes. 118 // User by protobuf JSON serialization. 119 func (t *TraceID) UnmarshalJSON(data []byte) error { 120 s := string(data) 121 if l := len(s); l > 2 && s[0] == '"' && s[l-1] == '"' { 122 s = s[1 : l-1] 123 } 124 b, err := base64.StdEncoding.DecodeString(s) 125 if err != nil { 126 return fmt.Errorf("cannot unmarshal TraceID from string '%s': %v", string(data), err) 127 } 128 return t.Unmarshal(b) 129 } 130 131 // ------- SpanID ------- 132 133 // NewSpanID creates a new SpanID from a 64bit unsigned int. 134 func NewSpanID(v uint64) SpanID { 135 return SpanID(v) 136 } 137 138 func (s SpanID) String() string { 139 return fmt.Sprintf("%x", uint64(s)) 140 } 141 142 // SpanIDFromString creates a SpanID from a hexadecimal string 143 func SpanIDFromString(s string) (SpanID, error) { 144 if len(s) > 16 { 145 return SpanID(0), fmt.Errorf("SpanID cannot be longer than 16 hex characters: %s", s) 146 } 147 id, err := strconv.ParseUint(s, 16, 64) 148 if err != nil { 149 return SpanID(0), err 150 } 151 return SpanID(id), nil 152 } 153 154 // MarshalText is called by encoding/json, which we do not want people to use. 155 func (s SpanID) MarshalText() ([]byte, error) { 156 return nil, fmt.Errorf("unsupported method SpanID.MarshalText; please use github.com/gogo/protobuf/jsonpb for marshalling") 157 } 158 159 // UnmarshalText is called by encoding/json, which we do not want people to use. 160 func (s *SpanID) UnmarshalText(text []byte) error { 161 return fmt.Errorf("unsupported method SpanID.UnmarshalText; please use github.com/gogo/protobuf/jsonpb for marshalling") 162 } 163 164 // Size returns the size of this datum in protobuf. It is always 8 bytes. 165 func (s *SpanID) Size() int { 166 return 8 167 } 168 169 // Marshal converts span ID into a binary representation. Called by protobuf serialization. 170 func (s SpanID) Marshal() ([]byte, error) { 171 b := make([]byte, s.Size()) 172 _, err := s.MarshalTo(b) 173 return b, err 174 } 175 176 // MarshalTo converts span ID into a binary representation. Called by protobuf serialization. 177 func (s *SpanID) MarshalTo(data []byte) (n int, err error) { 178 var b [8]byte 179 binary.BigEndian.PutUint64(b[:], uint64(*s)) 180 return marshalBytes(data, b[:]) 181 } 182 183 // Unmarshal inflates span ID from a binary representation. Called by protobuf serialization. 184 func (s *SpanID) Unmarshal(data []byte) error { 185 if len(data) < 8 { 186 return fmt.Errorf("buffer is too short") 187 } 188 *s = NewSpanID(binary.BigEndian.Uint64(data)) 189 return nil 190 } 191 192 // MarshalJSON converts span id into a base64 string enclosed in quotes. 193 // Used by protobuf JSON serialization. 194 // Example: {1} => "AAAAAAAAAAE=". 195 func (s SpanID) MarshalJSON() ([]byte, error) { 196 var b [8]byte 197 _, err := s.MarshalTo(b[:]) // can only error on incorrect buffer size 198 if err != nil { 199 return []byte{}, err 200 } 201 v := make([]byte, 12+2) 202 base64.StdEncoding.Encode(v[1:13], b[:]) 203 v[0], v[13] = '"', '"' 204 return v, nil 205 } 206 207 // UnmarshalJSON inflates span id from base64 string, possibly enclosed in quotes. 208 // User by protobuf JSON serialization. 209 // 210 // There appears to be a bug in gogoproto, as this function is only called for numeric values. 211 // https://github.com/gogo/protobuf/issues/411#issuecomment-393856837 212 func (s *SpanID) UnmarshalJSON(data []byte) error { 213 str := string(data) 214 if l := len(str); l > 2 && str[0] == '"' && str[l-1] == '"' { 215 str = str[1 : l-1] 216 } 217 b, err := base64.StdEncoding.DecodeString(str) 218 if err != nil { 219 return fmt.Errorf("cannot unmarshal SpanID from string '%s': %v", string(data), err) 220 } 221 return s.Unmarshal(b) 222 }