Files
Go_I2p/lib/common/data/mapping.go
2022-09-10 15:34:20 -04:00

176 lines
4.1 KiB
Go

package data
/*
I2P Mapping
https://geti2p.net/spec/common-structures#mapping
Accurate for version 0.9.24
+----+----+----+----+----+----+----+----+
| size |key_string (len + data) | = |
+----+----+----+----+----+----+----+----+
| val_string (len + data) | ; | ...
+----+----+----+----+----+----+----+
size :: Integer
length -> 2 bytes
Total number of bytes that follow
key_string :: String
A string (one byte length followed by UTF-8 encoded characters)
= :: A single byte containing '='
val_string :: String
A string (one byte length followed by UTF-8 encoded characters)
; :: A single byte containing ';'
*/
import (
"errors"
log "github.com/sirupsen/logrus"
)
type Mapping struct {
size *Integer
vals *MappingValues
}
//
// Returns the values contained in a Mapping in the form of a MappingValues.
//
func (mapping Mapping) Values() MappingValues {
if mapping.vals == nil {
return MappingValues{}
}
return *mapping.vals
}
func (mapping *Mapping) Data() []byte {
keyOrValIntegerLength := 1
bytes := mapping.size.Bytes()
for _, pair := range mapping.Values() {
klen, _ := pair[0].Length()
keylen, _ := NewIntegerFromInt(klen, keyOrValIntegerLength)
bytes = append(bytes, keylen.Bytes()...)
bytes = append(bytes, pair[0][1:]...)
bytes = append(bytes, 0x3d)
vlen, _ := pair[1].Length()
vallen, _ := NewIntegerFromInt(vlen, keyOrValIntegerLength)
bytes = append(bytes, vallen.Bytes()...)
bytes = append(bytes, pair[1][1:]...)
bytes = append(bytes, 0x3b)
}
return bytes
}
//
// Return true if two keys in a mapping are identical.
//
func (mapping *Mapping) HasDuplicateKeys() bool {
seen_values := make(map[string]bool)
values := mapping.Values()
for _, pair := range values {
key, _ := pair[0].Data()
if _, present := seen_values[key]; present {
return true
} else {
seen_values[key] = true
}
}
return false
}
//
// Convert a Go map of unformatted strings to a sorted Mapping.
//
func GoMapToMapping(gomap map[string]string) (mapping *Mapping, err error) {
map_vals := MappingValues{}
for k, v := range gomap {
key_str, kerr := ToI2PString(k)
if kerr != nil {
err = kerr
return
}
val_str, verr := ToI2PString(v)
if verr != nil {
err = verr
return
}
map_vals = append(
map_vals,
[2]I2PString{key_str, val_str},
)
}
mapping = ValuesToMapping(map_vals)
return
}
//
// Check if the string parsing error indicates that the Mapping
// should no longer be parsed.
//
func stopValueRead(err error) bool {
return err.Error() == "error parsing string: zero length"
}
//
// Determine if the first byte in a slice of bytes is the provided byte.
//
func beginsWith(bytes []byte, chr byte) bool {
return len(bytes) != 0 &&
bytes[0] == chr
}
func ReadMapping(bytes []byte) (mapping Mapping, remainder []byte, err []error) {
if len(bytes) == 0 {
log.WithFields(log.Fields{
"at": "ReadMapping",
"reason": "zero length",
}).Warn("mapping format violation")
e := errors.New("zero length")
err = append(err, e)
}
size, remainder, e := NewInteger(bytes, 2)
if e != nil {
err = append(err, e)
}
mapping.size = size
if e != nil {
log.WithFields(log.Fields{
"at": "ReadMapping",
"reason": "error parsing integer",
}).Warn("mapping format violation")
e := errors.New("error parsing integer")
err = append(err, e)
}
if len(remainder) == 0 {
log.WithFields(log.Fields{
"at": "ReadMapping",
"reason": "zero length",
}).Warn("mapping format violation")
e := errors.New("zero length")
err = append(err, e)
}
vals, remainder, mappingValueErrs := ReadMappingValues(bytes)
err = append(err, mappingValueErrs...)
mapping.vals = vals
if len(mappingValueErrs) > 0 {
log.WithFields(log.Fields{
"at": "ReadMapping",
"reason": "error parsing mapping values",
}).Warn("mapping format violation")
e := errors.New("error parsing mapping values")
err = append(err, e)
}
return
}
func NewMapping(bytes []byte) (values *Mapping, remainder []byte, err []error) {
objvalues, remainder, err := ReadMapping(bytes)
values = &objvalues
return
}