Initial commit
This commit is contained in:
		
							parent
							
								
									e5d5ba8530
								
							
						
					
					
						commit
						0a4c62f9e3
					
				
					 13 changed files with 598 additions and 0 deletions
				
			
		
							
								
								
									
										10
									
								
								SBHPFv1/constants.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								SBHPFv1/constants.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
package sbhpfv1
 | 
			
		||||
 | 
			
		||||
// Constants for file format.
 | 
			
		||||
// These are statically set in version 1 of SBHPF,
 | 
			
		||||
// but can be used to achieve an extended featureset in future versions,
 | 
			
		||||
// while maintaining backwards compatibility.
 | 
			
		||||
const (
 | 
			
		||||
	FormatVersion       = 0x01 // Current version
 | 
			
		||||
	ExtendedFeatureFlag = 0x00 // Reserved for future use
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										183
									
								
								SBHPFv1/deserializer.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								SBHPFv1/deserializer.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,183 @@
 | 
			
		|||
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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								SBHPFv1/node.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								SBHPFv1/node.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
package sbhpfv1
 | 
			
		||||
 | 
			
		||||
// Node is a hierarchical entity object with properties storing data.
 | 
			
		||||
type Node struct {
 | 
			
		||||
	Name       string
 | 
			
		||||
	Properties []Property
 | 
			
		||||
	Children   []*Node
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								SBHPFv1/property.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								SBHPFv1/property.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
package sbhpfv1
 | 
			
		||||
 | 
			
		||||
// Property defines a value, and belongs to a node.
 | 
			
		||||
type Property struct {
 | 
			
		||||
	Key   string
 | 
			
		||||
	Type  PropertyType
 | 
			
		||||
	Value interface{}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								SBHPFv1/property_type.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								SBHPFv1/property_type.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
package sbhpfv1
 | 
			
		||||
 | 
			
		||||
// PropertyType defines the type of a node property.
 | 
			
		||||
type PropertyType byte
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	TypeInt8    PropertyType = 0x01 // 8-bit signed integer. (1 byte)
 | 
			
		||||
	TypeUint8   PropertyType = 0x02 // 8-bit unsigned integer. (1 byte)
 | 
			
		||||
	TypeInt16   PropertyType = 0x03 // 16-bit signed integer. (2 bytes)
 | 
			
		||||
	TypeUint16  PropertyType = 0x04 // 16-bit unsigned integer. (2 bytes)
 | 
			
		||||
	TypeInt32   PropertyType = 0x05 // 32-bit signed integer. (4 bytes)
 | 
			
		||||
	TypeUint32  PropertyType = 0x06 // 32-bit unsigned integer. (4 bytes)
 | 
			
		||||
	TypeInt64   PropertyType = 0x07 // 64-bit signed integer. (8 bytes)
 | 
			
		||||
	TypeUint64  PropertyType = 0x08 // 64-bit unsigned integer. (8 bytes)
 | 
			
		||||
	TypeFloat32 PropertyType = 0x09 // 32-bit IEEE 754 single precision floating-point number. (4 bytes)
 | 
			
		||||
	TypeFloat64 PropertyType = 0x0a // 64-bit IEEE 754 single precision floating-point number. (8 bytes)
 | 
			
		||||
	TypeBool    PropertyType = 0x0b // 8-bit boolean. (1 byte)
 | 
			
		||||
	TypeString  PropertyType = 0x0c // Variably sized string. (2-byte length header + byte length specified by header)
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										166
									
								
								SBHPFv1/serializer.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								SBHPFv1/serializer.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,166 @@
 | 
			
		|||
package sbhpfv1
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/binary"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"io"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Serialize writes a full binary property file from a root node object.
 | 
			
		||||
// This includes the start headers necessary for proper deserialization of raw files.
 | 
			
		||||
func Serialize(w io.Writer, root *Node) error {
 | 
			
		||||
	// Write file header (version + feature flag).
 | 
			
		||||
	if err := binary.Write(w, binary.LittleEndian, uint8(FormatVersion)); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := binary.Write(w, binary.LittleEndian, uint8(ExtendedFeatureFlag)); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Serialize root node.
 | 
			
		||||
	return SerializeNode(w, root)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SerializeNode serializes a node object into a SBHPF node.
 | 
			
		||||
// NOTE: Writer must support seeking. If this is not possible, use SerializeNodeStream.
 | 
			
		||||
func SerializeNode(w io.Writer, node *Node) error {
 | 
			
		||||
	// Reserve space for node size ("Node Size" node header).
 | 
			
		||||
	sizePos := getWriterPosition(w)                                         // Save current position.
 | 
			
		||||
	if err := binary.Write(w, binary.LittleEndian, uint32(0)); err != nil { // uint32 to align with 4 byte constraint.
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Write property count ("Property Count" node header).
 | 
			
		||||
	propCount := uint16(len(node.Properties)) // uint16 to align with 2 byte constraint.
 | 
			
		||||
	if err := binary.Write(w, binary.LittleEndian, propCount); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Write child node count ("Child Count" node header).
 | 
			
		||||
	childCount := uint16(len(node.Children))
 | 
			
		||||
	if err := binary.Write(w, binary.LittleEndian, childCount); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Write node name length ("Name Length" node header).
 | 
			
		||||
	nameLen := uint8(len(node.Name)) // uint8 to align with 1 byte constraint.
 | 
			
		||||
	if err := binary.Write(w, binary.LittleEndian, nameLen); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Write node name if name length > 0 ("Node Name" node component).
 | 
			
		||||
	if nameLen > 0 {
 | 
			
		||||
		if _, err := w.Write([]byte(node.Name)); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Write properties.
 | 
			
		||||
	for _, prop := range node.Properties {
 | 
			
		||||
		if err := SerializeProperty(w, prop); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Write child nodes.
 | 
			
		||||
	for _, child := range node.Children {
 | 
			
		||||
		if err := SerializeNode(w, child); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Seek back and update node size.
 | 
			
		||||
	endPos := getWriterPosition(w)
 | 
			
		||||
	nodeSize := uint32(endPos - sizePos)
 | 
			
		||||
	return writeUint32AtPosition(w, sizePos, nodeSize)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SerializeProperty serializes a property object into a SBHPF property.
 | 
			
		||||
func SerializeProperty(w io.Writer, prop Property) error {
 | 
			
		||||
	// Write key length
 | 
			
		||||
	keyLen := uint8(len(prop.Key)) // uint8 to align with 1 byte constraint
 | 
			
		||||
	if err := binary.Write(w, binary.LittleEndian, keyLen); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Write type
 | 
			
		||||
	if err := binary.Write(w, binary.LittleEndian, prop.Type); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Write key
 | 
			
		||||
	if _, err := w.Write([]byte(prop.Key)); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Write value
 | 
			
		||||
	switch PropertyType(prop.Type) {
 | 
			
		||||
	case TypeString:
 | 
			
		||||
		strVal, ok := prop.Value.(string)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return errors.New("invalid type for string property")
 | 
			
		||||
		}
 | 
			
		||||
		strLen := uint16(len(strVal))
 | 
			
		||||
		if err := binary.Write(w, binary.LittleEndian, strLen); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		_, err := w.Write([]byte(strVal))
 | 
			
		||||
		return err
 | 
			
		||||
	case TypeBool:
 | 
			
		||||
		val, ok := prop.Value.(bool)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return errors.New("invalid type for boolean property")
 | 
			
		||||
		}
 | 
			
		||||
		return binary.Write(w, binary.LittleEndian, val)
 | 
			
		||||
	case TypeUint16:
 | 
			
		||||
		val, ok := prop.Value.(uint16)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return errors.New("invalid type for uint16 property")
 | 
			
		||||
		}
 | 
			
		||||
		return binary.Write(w, binary.LittleEndian, val)
 | 
			
		||||
	case TypeUint32:
 | 
			
		||||
		val, ok := prop.Value.(uint32)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return errors.New("invalid type for float32 property")
 | 
			
		||||
		}
 | 
			
		||||
		return binary.Write(w, binary.LittleEndian, val)
 | 
			
		||||
	case TypeUint8:
 | 
			
		||||
		val, ok := prop.Value.(uint8)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return errors.New("invalid type for uint8 property")
 | 
			
		||||
		}
 | 
			
		||||
		return binary.Write(w, binary.LittleEndian, val)
 | 
			
		||||
	case TypeInt16:
 | 
			
		||||
		val, ok := prop.Value.(int16)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return errors.New("invalid type for int16 property")
 | 
			
		||||
		}
 | 
			
		||||
		return binary.Write(w, binary.LittleEndian, val)
 | 
			
		||||
	case TypeInt32:
 | 
			
		||||
		val, ok := prop.Value.(int32)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return errors.New("invalid type for int32 property")
 | 
			
		||||
		}
 | 
			
		||||
		return binary.Write(w, binary.LittleEndian, val)
 | 
			
		||||
	case TypeInt8:
 | 
			
		||||
		val, ok := prop.Value.(int8)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return errors.New("invalid type for int8 property")
 | 
			
		||||
		}
 | 
			
		||||
		return binary.Write(w, binary.LittleEndian, val)
 | 
			
		||||
	case TypeFloat32:
 | 
			
		||||
		val, ok := prop.Value.(float32)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return errors.New("invalid type for float32 property")
 | 
			
		||||
		}
 | 
			
		||||
		return binary.Write(w, binary.LittleEndian, val)
 | 
			
		||||
	case TypeFloat64:
 | 
			
		||||
		val, ok := prop.Value.(float64)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return errors.New("invalid type for float64 property")
 | 
			
		||||
		}
 | 
			
		||||
		return binary.Write(w, binary.LittleEndian, val)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return errors.New("unsupported property type")
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										102
									
								
								SBHPFv1/serializer_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								SBHPFv1/serializer_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,102 @@
 | 
			
		|||
package sbhpfv1_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"io"
 | 
			
		||||
	"slices"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	sbhpfv1 "git.zervo.org/FLUX/GoSBHPF/SBHPFv1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	prop_ser_targetbytes = []byte{
 | 
			
		||||
		0x05,                         // Key length = 5
 | 
			
		||||
		0x0c,                         // Value type = string
 | 
			
		||||
		0x6f, 0x77, 0x6e, 0x65, 0x72, // Key = "owner"
 | 
			
		||||
		0x05, 0x00, // String length = 5
 | 
			
		||||
		0x7a, 0x65, 0x72, 0x76, 0x6f, // String = "zervo"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	node_ser_targetbytes = []byte{
 | 
			
		||||
		// ROOT NODE
 | 
			
		||||
		0x33, 0x00, 0x00, 0x00, // Node size = 33
 | 
			
		||||
		0x02, 0x00, // Property count = 2
 | 
			
		||||
		0x01, 0x00, // Child count = 1
 | 
			
		||||
		0x06,                               // Name length = 6
 | 
			
		||||
		0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, // Node name = "player"
 | 
			
		||||
		// ROOT NODE -> PROPERTY A
 | 
			
		||||
		0x06,                               // Key length = 6
 | 
			
		||||
		0x0b,                               // Value type = bool
 | 
			
		||||
		0x61, 0x63, 0x74, 0x69, 0x76, 0x65, // Key = "active"
 | 
			
		||||
		0x01, // Value = true
 | 
			
		||||
		// ROOT NODE -> PROPERTY B
 | 
			
		||||
		0x05,                         // Key length = 5
 | 
			
		||||
		0x03,                         // Value type = 16-bit signed int
 | 
			
		||||
		0x6c, 0x65, 0x76, 0x65, 0x6c, // Key = "level"
 | 
			
		||||
		0x1b, 0x00, // Value = 27
 | 
			
		||||
		// ROOT NODE -> CHILD NODE
 | 
			
		||||
		0x12, 0x00, 0x00, 0x00, // Node size = 12
 | 
			
		||||
		0x00, 0x00, // Property count = 0
 | 
			
		||||
		0x00, 0x00, // Child count = 0
 | 
			
		||||
		0x09,                                                 // Name length = 9
 | 
			
		||||
		0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, // Node name = "inventory"
 | 
			
		||||
	}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestPropertySerialization(t *testing.T) {
 | 
			
		||||
	prop := sbhpfv1.Property{
 | 
			
		||||
		Key:   "owner",
 | 
			
		||||
		Type:  sbhpfv1.TypeString,
 | 
			
		||||
		Value: "zervo",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	buf := &bytes.Buffer{}
 | 
			
		||||
	err := sbhpfv1.SerializeProperty(buf, prop)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Property serialization failed: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !slices.Equal(buf.Bytes(), prop_ser_targetbytes) {
 | 
			
		||||
		t.Fatalf("Property serialization generated bad data: %v", buf.Bytes())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestNodeSerialization(t *testing.T) {
 | 
			
		||||
	prop_a := sbhpfv1.Property{
 | 
			
		||||
		Key:   "active",
 | 
			
		||||
		Type:  sbhpfv1.TypeBool,
 | 
			
		||||
		Value: true,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	prop_b := sbhpfv1.Property{
 | 
			
		||||
		Key:   "level",
 | 
			
		||||
		Type:  sbhpfv1.TypeInt16,
 | 
			
		||||
		Value: int16(27),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	child_node := sbhpfv1.Node{
 | 
			
		||||
		Name: "inventory",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	node := sbhpfv1.Node{
 | 
			
		||||
		Name: "player",
 | 
			
		||||
		Properties: []sbhpfv1.Property{
 | 
			
		||||
			prop_a,
 | 
			
		||||
			prop_b,
 | 
			
		||||
		},
 | 
			
		||||
		Children: []*sbhpfv1.Node{
 | 
			
		||||
			&child_node,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	w := io.WriteSeeker{}
 | 
			
		||||
	err := sbhpfv1.SerializeNode(buf, &node)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Node serialization failed: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !slices.Equal(buf.Bytes(), node_ser_targetbytes) {
 | 
			
		||||
		t.Fatalf("Node serialization generated bad data: %v", buf.Bytes())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								SBHPFv1/streamed_serializer.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								SBHPFv1/streamed_serializer.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
package sbhpfv1
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"io"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SerializeNodeStream serializes a node object into a SBHPF node.
 | 
			
		||||
// It is a wrapper around SerializeNode that temporarily stores the serialized nodes in a memory buffer.
 | 
			
		||||
// This is less efficient, but also works with writers that lack seeking support.
 | 
			
		||||
func SerializeNodeStream(w io.Writer, node *Node) error {
 | 
			
		||||
	buf := &bytes.Buffer{}
 | 
			
		||||
	if err := SerializeNode(buf, node); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := io.Copy(w, buf)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								SBHPFv1/util.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								SBHPFv1/util.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,32 @@
 | 
			
		|||
package sbhpfv1
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/binary"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"io"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// getWriterPosition returns the current write position.
 | 
			
		||||
func getWriterPosition(w io.Writer) int64 {
 | 
			
		||||
	if seeker, ok := w.(io.Seeker); ok {
 | 
			
		||||
		pos, _ := seeker.Seek(0, io.SeekCurrent)
 | 
			
		||||
		return pos
 | 
			
		||||
	}
 | 
			
		||||
	return -1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// writeUint32AtPosition writes an uint32 at a given position.
 | 
			
		||||
func writeUint32AtPosition(w io.Writer, pos int64, value uint32) error {
 | 
			
		||||
	if seeker, ok := w.(io.Seeker); ok {
 | 
			
		||||
		curPos, _ := seeker.Seek(0, io.SeekCurrent) // Save current position
 | 
			
		||||
		if _, err := seeker.Seek(pos, io.SeekStart); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if err := binary.Write(w, binary.LittleEndian, value); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		_, _ = seeker.Seek(curPos, io.SeekStart) // Restore position
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return errors.New("writer does not support seeking")
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								go.mod
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								go.mod
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
module git.zervo.org/FLUX/GoSBHPF
 | 
			
		||||
 | 
			
		||||
go 1.23.5
 | 
			
		||||
							
								
								
									
										0
									
								
								go.sum
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								go.sum
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										48
									
								
								main.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								main.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,48 @@
 | 
			
		|||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"log"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	sbhpfv1 "git.zervo.org/FLUX/GoSBHPF/SBHPFv1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	file, err := os.Create("test.bin")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	defer file.Close()
 | 
			
		||||
 | 
			
		||||
	prop_a := sbhpfv1.Property{
 | 
			
		||||
		Key:   "active",
 | 
			
		||||
		Type:  sbhpfv1.TypeBool,
 | 
			
		||||
		Value: true,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	prop_b := sbhpfv1.Property{
 | 
			
		||||
		Key:   "level",
 | 
			
		||||
		Type:  sbhpfv1.TypeInt16,
 | 
			
		||||
		Value: int16(27),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	child_node := sbhpfv1.Node{
 | 
			
		||||
		Name: "inventory",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	node := sbhpfv1.Node{
 | 
			
		||||
		Name: "player",
 | 
			
		||||
		Properties: []sbhpfv1.Property{
 | 
			
		||||
			prop_a,
 | 
			
		||||
			prop_b,
 | 
			
		||||
		},
 | 
			
		||||
		Children: []*sbhpfv1.Node{
 | 
			
		||||
			&child_node,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = sbhpfv1.SerializeNode(file, &node)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								test.bin
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test.bin
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
		Loading…
	
	Add table
		
		Reference in a new issue