269 lines
5.8 KiB
Go
269 lines
5.8 KiB
Go
|
package wireproto
|
||
|
|
||
|
import (
|
||
|
`bytes`
|
||
|
`fmt`
|
||
|
`strings`
|
||
|
|
||
|
`github.com/google/uuid`
|
||
|
)
|
||
|
|
||
|
// ConnId returns a copy of the connection ID.
|
||
|
func (f *FieldValuePair) ConnId() (conn uuid.UUID) {
|
||
|
|
||
|
if f.connId == nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
conn = *f.connId
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// GetParent returns the parent Record, if set.
|
||
|
func (f *FieldValuePair) GetParent() (rec Record) {
|
||
|
|
||
|
rec = f.parent
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// MarshalBinary renders a FieldValuePair into a byte-packed format.
|
||
|
func (f *FieldValuePair) MarshalBinary() (data []byte, err error) {
|
||
|
|
||
|
var b []byte
|
||
|
var buf *bytes.Buffer = new(bytes.Buffer)
|
||
|
|
||
|
_ = f.Size()
|
||
|
|
||
|
if _, err = buf.Write(PackInt(f.Name.Size())); err != nil {
|
||
|
return
|
||
|
}
|
||
|
if _, err = buf.Write(PackInt(f.Value.Size())); err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if b, err = f.Name.MarshalBinary(); err != nil {
|
||
|
return
|
||
|
}
|
||
|
buf.Write(b)
|
||
|
if b, err = f.Value.MarshalBinary(); err != nil {
|
||
|
return
|
||
|
}
|
||
|
buf.Write(b)
|
||
|
|
||
|
data = buf.Bytes()
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Model returns an indented string representation of the model.
|
||
|
func (f *FieldValuePair) Model() (out string) {
|
||
|
|
||
|
out = f.ModelCustom(IndentChars, SeparatorChars, indentKvp)
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// ModelCustom is like Model with user-defined formatting.
|
||
|
func (f *FieldValuePair) ModelCustom(indent, sep string, level uint) (out string) {
|
||
|
|
||
|
var fnSize int
|
||
|
var fvSize int
|
||
|
var splitFn [][]byte // for multi-chunk hex
|
||
|
var splitFv [][]byte // """
|
||
|
var sb strings.Builder
|
||
|
var maxLen int = 8 // len of size uint32 hex
|
||
|
|
||
|
// This one is the biggest pain because it has to handle arbitrary lengths.
|
||
|
|
||
|
fnSize = f.Name.Size() * 2 // 2x because Hex string
|
||
|
fvSize = f.Value.Size() * 2 // """
|
||
|
if fnSize > maxLen {
|
||
|
if f.Name.Size() <= maxByteLine {
|
||
|
maxLen = fnSize
|
||
|
} else {
|
||
|
maxLen = maxByteLine * 2
|
||
|
}
|
||
|
}
|
||
|
if fvSize > maxLen {
|
||
|
if f.Value.Size() <= maxByteLine {
|
||
|
maxLen = fvSize
|
||
|
} else {
|
||
|
maxLen = maxByteLine * 2
|
||
|
}
|
||
|
}
|
||
|
|
||
|
splitFn = chunkByteLine(f.Name)
|
||
|
splitFv = chunkByteLine(f.Value)
|
||
|
|
||
|
// SIZES
|
||
|
// Field Name
|
||
|
// e.g. "\t\t\t00000003 // Field Name Size (3)"
|
||
|
sb.WriteString(strings.Repeat(indent, int(level))) // \t\t\t
|
||
|
sb.WriteString(padIntRight(f.Name.Size(), maxLen)) // "00000003 "
|
||
|
sb.WriteString(sep) // " "
|
||
|
sb.WriteString(fmt.Sprintf("// Field Name Size (%d)\n", f.Name.Size())) // "// Field Name Size (3)". The extra space is intentional.
|
||
|
// Field Value
|
||
|
// e.g. "\t\t\t00000018 // Field Value Size (24)"
|
||
|
sb.WriteString(strings.Repeat(indent, int(level))) // \t\t\t
|
||
|
sb.WriteString(padIntRight(f.Value.Size(), maxLen)) // "000000018 "
|
||
|
sb.WriteString(sep) // " "
|
||
|
sb.WriteString(fmt.Sprintf("// Field Value Size (%d)\n", f.Value.Size())) // "// Field Value Size (24)".
|
||
|
|
||
|
// VALUES
|
||
|
// Name
|
||
|
// e.g. `\t\t\t666f6f // "foo"`
|
||
|
for idx, chunk := range splitFn {
|
||
|
sb.WriteString(strings.Repeat(indent, int(level))) // "\t\t\t"
|
||
|
if idx == 0 {
|
||
|
sb.WriteString(padBytesRight(chunk, maxLen)) // "666f6f "
|
||
|
sb.WriteString(sep) // " "
|
||
|
sb.WriteString(fmt.Sprintf("// \"%s\"", f.Name)) // `// "foo"`
|
||
|
} else {
|
||
|
sb.WriteString(fmt.Sprintf("%x", chunk)) // "666f6f"
|
||
|
}
|
||
|
sb.WriteString("\n")
|
||
|
}
|
||
|
// Value
|
||
|
/*
|
||
|
e.g.:
|
||
|
\t\t\t736f6d65206c6f6e6765722076616c75 // "some longer value string"
|
||
|
\t\t\t6520737472696e67
|
||
|
*/
|
||
|
for idx, chunk := range splitFv {
|
||
|
sb.WriteString(strings.Repeat(indent, int(level))) // "\t\t\t"
|
||
|
if idx == 0 {
|
||
|
sb.WriteString(padBytesRight(chunk, maxLen)) // "736f6d65206c6f6e6765722076616c75"
|
||
|
sb.WriteString(sep) // " "
|
||
|
sb.WriteString(fmt.Sprintf("// \"%s\"", f.Value)) // `// "some longer value string"`
|
||
|
} else {
|
||
|
sb.WriteString(fmt.Sprintf("%x", chunk)) // "6520737472696e67"
|
||
|
}
|
||
|
sb.WriteString("\n")
|
||
|
}
|
||
|
|
||
|
out = sb.String()
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
ToMap returns a map of map[name]value.
|
||
|
While an interface{} in the map, the value is actually a FieldValue.
|
||
|
*/
|
||
|
func (f *FieldValuePair) ToMap() (m map[string]interface{}) {
|
||
|
|
||
|
m = map[string]interface{}{
|
||
|
f.Name.String(): f.Value,
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// UnmarshalBinary populates a FieldValuePair from packed bytes.
|
||
|
func (f *FieldValuePair) UnmarshalBinary(data []byte) (err error) {
|
||
|
|
||
|
if data == nil || len(data) == 0 {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
var b []byte
|
||
|
var nmSize, valSize int
|
||
|
var buf *bytes.Reader = bytes.NewReader(data)
|
||
|
|
||
|
if f == nil {
|
||
|
*f = FieldValuePair{}
|
||
|
}
|
||
|
f.common = &common{}
|
||
|
f.size = 0
|
||
|
|
||
|
// Get the name/value sizes.
|
||
|
b = make([]byte, PackedNumSize)
|
||
|
if _, err = buf.Read(b); err != nil {
|
||
|
return
|
||
|
}
|
||
|
nmSize = UnpackInt(b)
|
||
|
|
||
|
b = make([]byte, PackedNumSize)
|
||
|
if _, err = buf.Read(b); err != nil {
|
||
|
return
|
||
|
}
|
||
|
valSize = UnpackInt(b)
|
||
|
|
||
|
// Get the name.
|
||
|
if nmSize != 0 {
|
||
|
b = make([]byte, nmSize)
|
||
|
if _, err = buf.Read(b); err != nil {
|
||
|
return
|
||
|
}
|
||
|
if err = f.Name.UnmarshalBinary(b); err != nil {
|
||
|
return
|
||
|
}
|
||
|
} else {
|
||
|
f.Name = nil
|
||
|
}
|
||
|
|
||
|
// Get the value.
|
||
|
if valSize != 0 {
|
||
|
b = make([]byte, valSize)
|
||
|
if _, err = buf.Read(b); err != nil {
|
||
|
return
|
||
|
}
|
||
|
if err = f.Value.UnmarshalBinary(b); err != nil {
|
||
|
return
|
||
|
}
|
||
|
} else {
|
||
|
f.Value = nil
|
||
|
}
|
||
|
|
||
|
_ = f.Size()
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Size returns the FieldValuePair's calculated size (in bytes) and updates the size field.
|
||
|
func (f *FieldValuePair) Size() (size int) {
|
||
|
|
||
|
if f == nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Count and Size uint32s
|
||
|
size += PackedNumSize * 2
|
||
|
|
||
|
size += f.Name.Size()
|
||
|
size += f.Value.Size()
|
||
|
|
||
|
if f.common == nil {
|
||
|
f.common = new(common)
|
||
|
}
|
||
|
|
||
|
f.common.size = uint32(size)
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// getIdx returns the FVP index in the parent Record.
|
||
|
func (f *FieldValuePair) getIdx() (idx int) {
|
||
|
|
||
|
var fvps []FVP
|
||
|
|
||
|
idx = -1
|
||
|
|
||
|
if f == nil || f.parent == nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
fvps = f.parent.getFvps()
|
||
|
|
||
|
for i, fv := range fvps {
|
||
|
if fv == f {
|
||
|
idx = i
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|