package sbhpfv1 import ( "encoding/binary" "errors" "io" ) // Deserialize reads a full binary property file to a usable root node object. // This includes verifying the start headers to ensure compatibility. func Deserialize(r io.Reader) (*Node, error) { // Read file header (version + feature flag). var version, featureFlag uint8 if err := binary.Read(r, binary.LittleEndian, &version); err != nil { return nil, err } if err := binary.Read(r, binary.LittleEndian, &featureFlag); err != nil { return nil, err } // Validate version. if version != FormatVersion { return nil, errors.New("unsupported format version") } // Read root node. return DeserializeNode(r) } // DeserializeNode deserializes a SBHPF node into a usable node object. func DeserializeNode(r io.Reader) (*Node, error) { // Read node size ("Node Size" node header). // TODO: Node size header could possibly be removed entirely in version 2, // since properties terminate themselves and the node header already contains // info about amount of properties on the node. This would save 16 bytes per node. // The size header could potentially contribute to more efficient search algos though. var size uint32 // uint32 to align with 4 byte constraint. if err := binary.Read(r, binary.LittleEndian, &size); err != nil { return nil, err } // Read property count ("Property Count" node header). var propCount uint16 // uint16 to align with 2 byte constraint. if err := binary.Read(r, binary.LittleEndian, &propCount); err != nil { return nil, err } // Read child count ("Child Count" node header). var childCount uint16 if err := binary.Read(r, binary.LittleEndian, &childCount); err != nil { return nil, err } // Read node name length ("Name Length" node header). var nameLen uint8 // uint8 to align with 1 byte constraint. if err := binary.Read(r, binary.LittleEndian, &nameLen); err != nil { return nil, err } // Read node name component. nameBytes := make([]byte, nameLen) if _, err := io.ReadFull(r, nameBytes); err != nil { return nil, err } // Read properties. props := make([]Property, propCount) for i := uint16(0); i < propCount; i++ { // uint16 to align with 2 byte constraint of "Property Count" prop, err := DeserializeProperty(r) if err != nil { return nil, err } props[i] = prop } // Read child nodes. children := make([]*Node, childCount) for i := uint16(0); i < childCount; i++ { child, err := DeserializeNode(r) if err != nil { return nil, err } children[i] = child } return &Node{Name: string(nameBytes), Properties: props, Children: children}, nil } // DeserializeProperty deserializes a SBHPF property into a usable property object. func DeserializeProperty(r io.Reader) (Property, error) { // Read key length var keyLen uint8 // uint8 to align with 1 byte constraint if err := binary.Read(r, binary.LittleEndian, &keyLen); err != nil { return Property{}, err } // Read type var propType PropertyType // PropertyType (byte, uint8) to align with 1 byte and values constraint if err := binary.Read(r, binary.LittleEndian, &propType); err != nil { return Property{}, err } // Read key string keyBytes := make([]byte, keyLen) if _, err := io.ReadFull(r, keyBytes); err != nil { return Property{}, err } // Read value var value interface{} switch PropertyType(propType) { case TypeString: var strLen uint16 // uint16 to align with 2 byte length-prefix constraint if err := binary.Read(r, binary.LittleEndian, &strLen); err != nil { return Property{}, err } strBytes := make([]byte, strLen) if _, err := io.ReadFull(r, strBytes); err != nil { return Property{}, err } value = string(strBytes) case TypeBool: var val uint8 // uint8 to align with 1 byte constraint if err := binary.Read(r, binary.LittleEndian, &val); err != nil { return Property{}, err } value = (val == 1) case TypeUint16: var val uint16 if err := binary.Read(r, binary.LittleEndian, &val); err != nil { return Property{}, err } value = val case TypeUint32: var val uint32 if err := binary.Read(r, binary.LittleEndian, &val); err != nil { return Property{}, err } value = val case TypeUint8: var val uint8 if err := binary.Read(r, binary.LittleEndian, &val); err != nil { return Property{}, err } value = val case TypeInt16: var val int16 if err := binary.Read(r, binary.LittleEndian, &val); err != nil { return Property{}, err } value = val case TypeInt32: var val int32 if err := binary.Read(r, binary.LittleEndian, &val); err != nil { return Property{}, err } value = val case TypeInt8: var val int8 if err := binary.Read(r, binary.LittleEndian, &val); err != nil { return Property{}, err } value = val case TypeFloat32: var val float32 if err := binary.Read(r, binary.LittleEndian, &val); err != nil { return Property{}, err } value = val case TypeFloat64: var val float64 if err := binary.Read(r, binary.LittleEndian, &val); err != nil { return Property{}, err } value = val } return Property{ Key: string(keyBytes), Type: propType, Value: value, }, nil }