1
0
mirror of https://github.com/garraflavatra/go-fmp.git synced 2025-06-27 20:15:11 +00:00

Compare commits

...

6 Commits

Author SHA1 Message Date
c08c429b99 Add NewSector function 2025-06-22 20:08:53 +02:00
4ab7f0a588 Add date/time format constants 2025-06-22 19:20:41 +02:00
7cf3fd93ac Record parsing 2025-06-22 19:14:22 +02:00
d22b209ca5 Row parsing 2025-06-22 18:43:06 +02:00
7359962d98 Add calculation constants 2025-06-16 11:40:09 +02:00
c261a15041 Parse field repetition count 2025-06-16 11:29:48 +02:00
9 changed files with 343 additions and 150 deletions

Binary file not shown.

View File

@ -14,6 +14,12 @@ var (
ErrBadChunk = FmpError("bad chunk") ErrBadChunk = FmpError("bad chunk")
) )
const (
FmpDateLayout = "02/01/2006"
FmpTimeLayout = "15:04:05"
FmpDateTimeLayout = "02/01/2006 15:04:05"
)
type FmpChunkType uint8 type FmpChunkType uint8
const ( const (
@ -97,6 +103,24 @@ var autoEnterOptionMap = map[uint8]FmpAutoEnterOption{
136: FmpAutoEnterCalculationReplacingExistingValue, 136: FmpAutoEnterCalculationReplacingExistingValue,
} }
type FmpCalculationOperator byte
const (
FmpCalcOperatorAdd FmpCalculationOperator = '+'
FmpCalcOperatorSubtract FmpCalculationOperator = '-'
FmpCalcOperatorMultiply FmpCalculationOperator = '*'
FmpCalcOperatorDivide FmpCalculationOperator = '/'
FmpCalcOperatorConcatenate FmpCalculationOperator = '&'
)
// var calcOperatorMap = map[uint8]FmpCalculationOperator{
// 0x25: FmpCalcOperatorAdd,
// 0x26: FmpCalcOperatorSubtract,
// 0x27: FmpCalcOperatorMultiply,
// 0x28: FmpCalcOperatorDivide,
// 0x50: FmpCalcOperatorConcatenate,
// }
type FmpScriptStepType uint64 type FmpScriptStepType uint64
const ( const (

View File

@ -3,6 +3,7 @@ package fmp
import ( import (
"fmt" "fmt"
"os" "os"
"slices"
) )
const debugging = false const debugging = false
@ -80,7 +81,15 @@ func (c *FmpChunk) String() string {
func (dict *FmpDict) string(parentPath string) string { func (dict *FmpDict) string(parentPath string) string {
s := "" s := ""
for k, v := range *dict { keys := make([]uint64, 0, len(*dict))
for k := range *dict {
keys = append(keys, k)
}
slices.Sort(keys)
for _, k := range keys {
v := (*dict)[k]
s += fmt.Sprintf("%v%v: %v\n", parentPath, k, string(v.Value)) s += fmt.Sprintf("%v%v: %v\n", parentPath, k, string(v.Value))
if v.Children != nil { if v.Children != nil {

58
fmp/fmp_dict.go Normal file
View File

@ -0,0 +1,58 @@
package fmp
type FmpDict map[uint64]*FmpDictEntry
type FmpDictEntry struct {
Value []byte
Children *FmpDict
}
func (dict *FmpDict) GetEntry(path ...uint64) *FmpDictEntry {
for i, key := range path {
_, ok := (*dict)[key]
if !ok {
return nil
}
if i == len(path)-1 {
return (*dict)[key]
} else {
dict = (*dict)[key].Children
if dict == nil {
return nil
}
}
}
return nil
}
func (dict *FmpDict) GetValue(path ...uint64) []byte {
ent := dict.GetEntry(path...)
if ent != nil {
return ent.Value
}
return nil
}
func (dict *FmpDict) GetChildren(path ...uint64) *FmpDict {
ent := dict.GetEntry(path...)
if ent != nil {
return ent.Children
}
return &FmpDict{}
}
func (dict *FmpDict) set(path []uint64, value []byte) {
for i, key := range path {
_, ok := (*dict)[key]
if !ok {
(*dict)[key] = &FmpDictEntry{Children: &FmpDict{}}
}
if i == len(path)-1 {
(*dict)[key].Value = value
} else {
dict = (*dict)[key].Children
}
}
}

View File

@ -28,10 +28,11 @@ type FmpFile struct {
Chunks []*FmpChunk Chunks []*FmpChunk
Dictionary *FmpDict Dictionary *FmpDict
tables []*FmpTable
numSectors uint64 // Excludes the header sector numSectors uint64 // Excludes the header sector
currentSectorID uint64 currentSectorID uint64
stream io.ReadSeeker stream *os.File
} }
func OpenFile(path string) (*FmpFile, error) { func OpenFile(path string) (*FmpFile, error) {
@ -40,11 +41,10 @@ func OpenFile(path string) (*FmpFile, error) {
return nil, err return nil, err
} }
stream, err := os.Open(path) stream, err := os.OpenFile(path, os.O_RDWR, 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer stream.Close()
ctx := &FmpFile{stream: stream, Dictionary: &FmpDict{}} ctx := &FmpFile{stream: stream, Dictionary: &FmpDict{}}
if err := ctx.readHeader(); err != nil { if err := ctx.readHeader(); err != nil {
@ -85,9 +85,14 @@ func OpenFile(path string) (*FmpFile, error) {
} }
} }
ctx.readTables()
return ctx, nil return ctx, nil
} }
func (ctx *FmpFile) Close() {
ctx.stream.Close()
}
func (ctx *FmpFile) readHeader() error { func (ctx *FmpFile) readHeader() error {
buf := make([]byte, headerSize) buf := make([]byte, headerSize)
_, err := ctx.stream.Read(buf) _, err := ctx.stream.Read(buf)
@ -128,8 +133,8 @@ func (ctx *FmpFile) readSector() (*FmpSector, error) {
ID: ctx.currentSectorID, ID: ctx.currentSectorID,
Deleted: buf[0] > 0, Deleted: buf[0] > 0,
Level: uint8(buf[1]), Level: uint8(buf[1]),
PrevID: parseVarUint64(buf[4 : 4+4]), PrevID: decodeVarUint64(buf[4 : 4+4]),
NextID: parseVarUint64(buf[8 : 8+4]), NextID: decodeVarUint64(buf[8 : 8+4]),
Chunks: make([]*FmpChunk, 0), Chunks: make([]*FmpChunk, 0),
} }
@ -148,3 +153,101 @@ func (ctx *FmpFile) readSector() (*FmpSector, error) {
} }
return sector, nil return sector, nil
} }
func (ctx *FmpFile) readTables() {
tables := make([]*FmpTable, 0)
ent := ctx.Dictionary.GetEntry(3, 16, 5)
for path, tableEnt := range *ent.Children {
if path < 128 {
continue
}
table := &FmpTable{
ID: path,
Name: decodeString(tableEnt.Children.GetValue(16)),
Columns: map[uint64]*FmpColumn{},
Records: map[uint64]*FmpRecord{},
}
tables = append(tables, table)
for colPath, colEnt := range *ctx.Dictionary.GetChildren(table.ID, 3, 5) {
name := decodeString(colEnt.Children.GetValue(16))
flags := colEnt.Children.GetValue(2)
column := &FmpColumn{
Index: colPath,
Name: name,
Type: FmpFieldType(flags[0]),
DataType: FmpDataType(flags[1]),
StorageType: FmpFieldStorageType(flags[9]),
Repetitions: flags[25],
Indexed: flags[8] == 128,
}
if flags[11] == 1 {
column.AutoEnter = autoEnterPresetMap[flags[4]]
} else {
column.AutoEnter = autoEnterOptionMap[flags[11]]
}
table.Columns[column.Index] = column
}
for recPath, recEnt := range *ctx.Dictionary.GetChildren(table.ID, 5) {
record := &FmpRecord{Index: recPath, Values: make(map[uint64]string)}
table.Records[record.Index] = record
if recPath > table.lastRecordID {
table.lastRecordID = recPath
}
for colIndex, value := range *recEnt.Children {
record.Values[colIndex] = decodeString(value.Value)
}
}
}
ctx.tables = tables
}
func (ctx *FmpFile) NewSector() (*FmpSector, error) {
id := uint64(len(ctx.Sectors)) + 1
prevID := id - 2
ctx.Sectors[prevID].NextID = uint64(id)
_, err := ctx.stream.WriteAt(encodeUint(4, int(id)), int64((id-1)*sectorSize)+4)
if err != nil {
return nil, err
}
sector := &FmpSector{
ID: uint64(id),
Deleted: false,
Level: 0,
PrevID: prevID,
NextID: 0,
Chunks: make([]*FmpChunk, 0),
}
ctx.Sectors = append(ctx.Sectors, sector)
sectorBuf := make([]byte, sectorSize)
sectorBuf[0] = 0 // deleted
sectorBuf[1] = 0 // level
writeToSlice(sectorBuf, 4, encodeUint(4, int(prevID))...)
writeToSlice(sectorBuf, 8, encodeUint(4, int(id))...)
_, err = ctx.stream.WriteAt(sectorBuf, int64((id+1)*sectorSize))
if err != nil {
return nil, err
}
return sector, nil
}
func (ctx *FmpFile) setValue(path []uint64, value []byte) {
// ctx.Dictionary.set(path, value)
}

View File

@ -70,7 +70,7 @@ func (sect *FmpSector) processChunks(dict *FmpDict) error {
for _, chunk := range sect.Chunks { for _, chunk := range sect.Chunks {
switch chunk.Type { switch chunk.Type {
case FmpChunkPathPush, FmpChunkPathPushLong: case FmpChunkPathPush, FmpChunkPathPushLong:
currentPath = append(currentPath, parseVarUint64(chunk.Value)) currentPath = append(currentPath, decodeVarUint64(chunk.Value))
dumpPath(currentPath) dumpPath(currentPath)
case FmpChunkPathPop: case FmpChunkPathPop:
@ -79,23 +79,23 @@ func (sect *FmpSector) processChunks(dict *FmpDict) error {
} }
case FmpChunkSimpleData: case FmpChunkSimpleData:
dict.SetValue(currentPath, chunk.Value) dict.set(currentPath, chunk.Value)
case FmpChunkSegmentedData: case FmpChunkSegmentedData:
// Todo: take index into account // Todo: take index into account
dict.SetValue( dict.set(
currentPath, currentPath,
append(dict.GetValue(currentPath...), chunk.Value...), append(dict.GetValue(currentPath...), chunk.Value...),
) )
case FmpChunkSimpleKeyValue: case FmpChunkSimpleKeyValue:
dict.SetValue( dict.set(
append(currentPath, uint64(chunk.Key)), append(currentPath, uint64(chunk.Key)),
chunk.Value, chunk.Value,
) )
case FmpChunkLongKeyValue: case FmpChunkLongKeyValue:
dict.SetValue( dict.set(
append(currentPath, uint64(chunk.Key)), // todo: ?? append(currentPath, uint64(chunk.Key)), // todo: ??
chunk.Value, chunk.Value,
) )
@ -134,7 +134,7 @@ func (sect *FmpSector) readChunk(payload []byte) (*FmpChunk, error) {
chunk.Value = payload[3:chunk.Length] chunk.Value = payload[3:chunk.Length]
case 0x07: case 0x07:
valueLength := parseVarUint64(payload[2 : 2+2]) valueLength := decodeVarUint64(payload[2 : 2+2])
chunk.Length = min(4+valueLength, uint64(len(payload))) chunk.Length = min(4+valueLength, uint64(len(payload)))
chunk.Type = FmpChunkSegmentedData chunk.Type = FmpChunkSegmentedData
chunk.Index = uint64(payload[1]) chunk.Index = uint64(payload[1])
@ -148,13 +148,13 @@ func (sect *FmpSector) readChunk(payload []byte) (*FmpChunk, error) {
case 0x09: case 0x09:
chunk.Length = 4 chunk.Length = 4
chunk.Type = FmpChunkSimpleKeyValue chunk.Type = FmpChunkSimpleKeyValue
chunk.Key = parseVarUint64(payload[1 : 1+2]) chunk.Key = decodeVarUint64(payload[1 : 1+2])
chunk.Value = payload[3:chunk.Length] chunk.Value = payload[3:chunk.Length]
case 0x0A, 0x0B, 0x0C, 0x0D: case 0x0A, 0x0B, 0x0C, 0x0D:
chunk.Length = 3 + 2*uint64(chunkCode-0x09) chunk.Length = 3 + 2*uint64(chunkCode-0x09)
chunk.Type = FmpChunkSimpleKeyValue chunk.Type = FmpChunkSimpleKeyValue
chunk.Key = parseVarUint64(payload[1 : 1+2]) chunk.Key = decodeVarUint64(payload[1 : 1+2])
chunk.Value = payload[3:chunk.Length] chunk.Value = payload[3:chunk.Length]
case 0x0E: case 0x0E:
@ -167,17 +167,17 @@ func (sect *FmpSector) readChunk(payload []byte) (*FmpChunk, error) {
chunk.Length = 4 + uint64(payload[3]) chunk.Length = 4 + uint64(payload[3])
chunk.Type = FmpChunkSimpleKeyValue chunk.Type = FmpChunkSimpleKeyValue
chunk.Key = parseVarUint64(payload[1 : 1+2]) chunk.Key = decodeVarUint64(payload[1 : 1+2])
chunk.Value = payload[4:chunk.Length] chunk.Value = payload[4:chunk.Length]
case 0x0F: case 0x0F:
valueLength := parseVarUint64(payload[3 : 3+2]) valueLength := decodeVarUint64(payload[3 : 3+2])
chunk.Length = uint64(len(payload)) chunk.Length = uint64(len(payload))
if chunk.Length > 5+valueLength { if chunk.Length > 5+valueLength {
return nil, ErrBadChunk return nil, ErrBadChunk
} }
chunk.Type = FmpChunkSegmentedData chunk.Type = FmpChunkSegmentedData
chunk.Index = parseVarUint64(payload[1 : 1+2]) chunk.Index = decodeVarUint64(payload[1 : 1+2])
chunk.Value = payload[5:chunk.Length] chunk.Value = payload[5:chunk.Length]
case 0x10, 0x11: case 0x10, 0x11:
@ -193,13 +193,13 @@ func (sect *FmpSector) readChunk(payload []byte) (*FmpChunk, error) {
case 0x16: case 0x16:
chunk.Length = 5 + uint64(payload[4]) chunk.Length = 5 + uint64(payload[4])
chunk.Type = FmpChunkLongKeyValue chunk.Type = FmpChunkLongKeyValue
chunk.Key = parseVarUint64(payload[1 : 1+3]) chunk.Key = decodeVarUint64(payload[1 : 1+3])
chunk.Value = payload[5:chunk.Length] chunk.Value = payload[5:chunk.Length]
case 0x17: case 0x17:
chunk.Length = 6 + parseVarUint64(payload[4:4+2]) chunk.Length = 6 + decodeVarUint64(payload[4:4+2])
chunk.Type = FmpChunkLongKeyValue chunk.Type = FmpChunkLongKeyValue
chunk.Key = parseVarUint64(payload[1 : 1+3]) chunk.Key = decodeVarUint64(payload[1 : 1+3])
chunk.Value = payload[6:chunk.Length] chunk.Value = payload[6:chunk.Length]
case 0x19, 0x1A, 0x1B, 0x1C, 0x1D: case 0x19, 0x1A, 0x1B, 0x1C, 0x1D:
@ -213,15 +213,15 @@ func (sect *FmpSector) readChunk(payload []byte) (*FmpChunk, error) {
valueLength := uint64(payload[2+keyLength]) valueLength := uint64(payload[2+keyLength])
chunk.Length = 2 + keyLength + 1 + valueLength chunk.Length = 2 + keyLength + 1 + valueLength
chunk.Type = FmpChunkLongKeyValue chunk.Type = FmpChunkLongKeyValue
chunk.Key = parseVarUint64(payload[2 : 2+keyLength]) chunk.Key = decodeVarUint64(payload[2 : 2+keyLength])
chunk.Value = payload[2+keyLength+1 : chunk.Length] chunk.Value = payload[2+keyLength+1 : chunk.Length]
case 0x1F: case 0x1F:
keyLength := uint64(payload[1]) keyLength := uint64(payload[1])
valueLength := parseVarUint64(payload[2+keyLength : 2+keyLength+2+1]) valueLength := decodeVarUint64(payload[2+keyLength : 2+keyLength+2+1])
chunk.Length = 2 + keyLength + 2 + valueLength chunk.Length = 2 + keyLength + 2 + valueLength
chunk.Type = FmpChunkLongKeyValue chunk.Type = FmpChunkLongKeyValue
chunk.Key = parseVarUint64(payload[2 : 2+keyLength]) chunk.Key = decodeVarUint64(payload[2 : 2+keyLength])
chunk.Value = payload[2+keyLength+2 : chunk.Length] chunk.Value = payload[2+keyLength+2 : chunk.Length]
case 0x20, 0xE0: case 0x20, 0xE0:

View File

@ -3,59 +3,61 @@ package fmp
type FmpTable struct { type FmpTable struct {
ID uint64 ID uint64
Name string Name string
Columns []FmpColumn Columns map[uint64]*FmpColumn
Records map[uint64]*FmpRecord
lastRecordID uint64
} }
type FmpColumn struct { type FmpColumn struct {
ID uint64 Index uint64
Name string Name string
Type FmpFieldType Type FmpFieldType
DataType FmpDataType DataType FmpDataType
StorageType FmpFieldStorageType StorageType FmpFieldStorageType
AutoEnter FmpAutoEnterOption AutoEnter FmpAutoEnterOption
Repetitions uint8
Indexed bool Indexed bool
} }
func (ctx *FmpFile) Tables() []*FmpTable { type FmpRecord struct {
tables := make([]*FmpTable, 0) Table *FmpTable
ent := ctx.Dictionary.GetEntry(3, 16, 5) Index uint64
Values map[uint64]string
}
for path, tableEnt := range *ent.Children { func (ctx *FmpFile) Table(name string) *FmpTable {
if path < 128 { for _, table := range ctx.tables {
continue if table.Name == name {
} return table
table := &FmpTable{
ID: path,
Name: decodeByteSeq(tableEnt.Children.GetValue(16)),
Columns: make([]FmpColumn, 0),
}
tables = append(tables, table)
colEnt := ctx.Dictionary.GetEntry(table.ID, 3, 5)
for colPath, colEnt := range *colEnt.Children {
name := decodeByteSeq(colEnt.Children.GetValue(16))
flags := colEnt.Children.GetValue(2)
column := FmpColumn{
ID: colPath,
Name: name,
Type: FmpFieldType(flags[0]),
DataType: FmpDataType(flags[1]),
StorageType: FmpFieldStorageType(flags[9]),
Indexed: flags[8] == 128,
}
if flags[11] == 1 {
column.AutoEnter = autoEnterPresetMap[flags[4]]
} else {
column.AutoEnter = autoEnterOptionMap[flags[11]]
}
table.Columns = append(table.Columns, column)
} }
} }
return nil
return tables }
func (t *FmpTable) Column(name string) *FmpColumn {
for _, column := range t.Columns {
if column.Name == name {
return column
}
}
return nil
}
func (t *FmpTable) NewRecord(values map[string]string) (*FmpRecord, error) {
vals := make(map[uint64]string)
for k, v := range values {
col := t.Column(k)
vals[col.Index] = v
}
id := t.lastRecordID + 1
t.lastRecordID = id
t.Records[id] = &FmpRecord{Table: t, Index: id, Values: vals}
return t.Records[id], nil
}
func (r *FmpRecord) Value(name string) string {
return r.Values[r.Table.Column(name).Index]
} }

View File

@ -1,17 +1,22 @@
package fmp package fmp
import "testing" import (
"slices"
"testing"
)
func TestOpenFile(t *testing.T) { func TestOpenFile(t *testing.T) {
f, err := OpenFile("../files/Untitled.fmp12") f, err := OpenFile("../files/Untitled.fmp12")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if f.FileSize != 393216 { defer f.Close()
t.Errorf("expected file size to be 393216, got %d", f.FileSize)
if f.FileSize != 229376 {
t.Errorf("expected file size to be 229376, got %d", f.FileSize)
} }
if f.numSectors != 95 { if f.numSectors != 55 {
t.Errorf("expected number of sectors to be 95, got %d", f.numSectors) t.Errorf("expected number of sectors to be 55, got %d", f.numSectors)
} }
if f.CreatorName != "Pro 12.0" { if f.CreatorName != "Pro 12.0" {
t.Errorf("expected application name to be 'Pro 12.0', got '%s'", f.CreatorName) t.Errorf("expected application name to be 'Pro 12.0', got '%s'", f.CreatorName)
@ -19,6 +24,7 @@ func TestOpenFile(t *testing.T) {
if f.VersionDate.Format("2006-01-02") != "2025-01-11" { if f.VersionDate.Format("2006-01-02") != "2025-01-11" {
t.Errorf("expected version date to be '2025-01-11', got '%s'", f.VersionDate.Format("2006-01-02")) t.Errorf("expected version date to be '2025-01-11', got '%s'", f.VersionDate.Format("2006-01-02"))
} }
f.ToDebugFile("../private/output") f.ToDebugFile("../private/output")
} }
@ -27,41 +33,81 @@ func TestTables(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
tables := f.Tables() defer f.Close()
expectedNames := []string{"Untitled", "WayDomains", "WayProcesses"} expectedNames := []string{"Untitled"}
tableNames := []string{} tableNames := []string{}
for _, table := range tables { for _, table := range f.tables {
tableNames = append(tableNames, table.Name) tableNames = append(tableNames, table.Name)
} }
if !slicesHaveSameElements(tableNames, expectedNames) { if !slicesHaveSameElements(tableNames, expectedNames) {
t.Errorf("tables do not match") t.Errorf("tables do not match")
} }
var field FmpColumn table := f.Table("Untitled")
for _, table := range tables { if table == nil {
for _, column := range table.Columns { t.Errorf("expected table to exist, but it does not")
if column.Name == "PrimaryKey" { return
field = column }
break if table.Name != "Untitled" {
} t.Errorf("expected table name to be 'Untitled', but it is '%s'", table.Name)
} }
if len(table.Records) != 3 {
t.Errorf("expected table to have 3 records, but it has %d", len(table.Records))
}
if table.Records[1].Values[1] != "629FAA83-50D8-401F-A560-C8D45217D17B" {
t.Errorf("first record has an incorrect ID '%s'", table.Records[0].Values[0])
} }
if field.Type != FmpFieldSimple { col := table.Column("PrimaryKey")
if col == nil {
t.Errorf("expected column to exist, but it does not")
return
}
if col.Name != "PrimaryKey" {
t.Errorf("expected column name to be 'PrimaryKey', but it is '%s'", col.Name)
}
if col.Type != FmpFieldSimple {
t.Errorf("expected field type to be simple, but it is not") t.Errorf("expected field type to be simple, but it is not")
} }
if field.DataType != FmpDataText { if col.DataType != FmpDataText {
t.Errorf("expected field data type to be text, but it is not") t.Errorf("expected field data type to be text, but it is not")
} }
if field.StorageType != FmpFieldStorageRegular { if col.StorageType != FmpFieldStorageRegular {
t.Errorf("expected field storage type to be regular, but it is not") t.Errorf("expected field storage type to be regular, but it is not")
} }
if !field.Indexed { if col.Repetitions != 1 {
t.Errorf("expected field repetition count to be 1, but it is %d", col.Repetitions)
}
if !col.Indexed {
t.Errorf("expected field to be indexed, but it is not") t.Errorf("expected field to be indexed, but it is not")
} }
if field.AutoEnter != FmpAutoEnterCalculationReplacingExistingValue { if col.AutoEnter != FmpAutoEnterCalculationReplacingExistingValue {
t.Errorf("expected field to have auto enter calculation replacing existing value, but it does not") t.Errorf("expected field to have auto enter calculation replacing existing value, but it does not")
} }
newRecord, err := table.NewRecord(map[string]string{"PrimaryKey": "629FAA83-50D8-401F-A560-C8D45217D17B"})
if newRecord == nil || err != nil {
t.Errorf("expected new record to be created, but it is nil")
return
}
if newRecord.Index != 4 {
t.Errorf("expected new record index to be 4, but it is %d", newRecord.Index)
}
if newRecord.Value("PrimaryKey") != "629FAA83-50D8-401F-A560-C8D45217D17B" {
t.Errorf("expected new record primary key to be '629FAA83-50D8-401F-A560-C8D45217D17B', but it is '%s'", newRecord.Value("PrimaryKey"))
}
}
func slicesHaveSameElements[Type comparable](a, b []Type) bool {
if len(a) != len(b) {
return false
}
for _, av := range a {
found := slices.Contains(b, av)
if !found {
return false
}
}
return true
} }

View File

@ -1,57 +1,13 @@
package fmp package fmp
import "slices" func addIf(cond bool, val uint64) uint64 {
if cond {
type FmpDict map[uint64]*FmpDictEntry return val
type FmpDictEntry struct {
Value []byte
Children *FmpDict
}
func (dict *FmpDict) GetEntry(path ...uint64) *FmpDictEntry {
for i, key := range path {
_, ok := (*dict)[key]
if !ok {
return nil
}
if i == len(path)-1 {
return (*dict)[key]
} else {
dict = (*dict)[key].Children
if dict == nil {
return nil
}
}
} }
return nil return 0
} }
func (dict *FmpDict) GetValue(path ...uint64) []byte { func decodeVarUint64(payload []byte) uint64 {
ent := dict.GetEntry(path...)
if ent != nil {
return ent.Value
}
return nil
}
func (dict *FmpDict) SetValue(path []uint64, value []byte) {
for i, key := range path {
_, ok := (*dict)[key]
if !ok {
(*dict)[key] = &FmpDictEntry{Children: &FmpDict{}}
}
if i == len(path)-1 {
(*dict)[key].Value = value
} else {
dict = (*dict)[key].Children
}
}
}
func parseVarUint64(payload []byte) uint64 {
var length uint64 var length uint64
n := min(len(payload), 8) // clamp to uint64 n := min(len(payload), 8) // clamp to uint64
for i := range n { for i := range n {
@ -61,7 +17,7 @@ func parseVarUint64(payload []byte) uint64 {
return length return length
} }
func decodeByteSeq(payload []byte) string { func decodeString(payload []byte) string {
result := "" result := ""
for i := range payload { for i := range payload {
result += string(payload[i] ^ 0x5A) result += string(payload[i] ^ 0x5A)
@ -69,22 +25,17 @@ func decodeByteSeq(payload []byte) string {
return result return result
} }
func addIf(cond bool, val uint64) uint64 { func encodeUint(size uint, value int) []byte {
if cond { result := make([]byte, size)
return val for i := range size {
result[i] = byte(value & 0xFF)
value >>= 8
} }
return 0 return result
} }
func slicesHaveSameElements[Type comparable](a, b []Type) bool { func writeToSlice(slice []byte, start int, payload ...byte) {
if len(a) != len(b) { for i := range payload {
return false slice[start+i] = payload[i]
} }
for _, av := range a {
found := slices.Contains(b, av)
if !found {
return false
}
}
return true
} }