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