140 lines
3.9 KiB
Go
140 lines
3.9 KiB
Go
package betterbinary
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"math"
|
|
|
|
"github.com/nbd-wtf/go-nostr"
|
|
)
|
|
|
|
const (
|
|
MaxKind = math.MaxUint16
|
|
MaxCreatedAt = math.MaxUint32
|
|
MaxContentSize = math.MaxUint16
|
|
MaxTagCount = math.MaxUint16
|
|
MaxTagItemCount = math.MaxUint8
|
|
MaxTagItemSize = math.MaxUint16
|
|
)
|
|
|
|
func Measure(evt nostr.Event) int {
|
|
n := 135 // static base
|
|
|
|
n += 2 + // tag section length
|
|
2 + // number of tags
|
|
len(evt.Tags)*3 // each tag offset + each tag item count
|
|
for _, tag := range evt.Tags {
|
|
n += len(tag) * 2 // item length for each item in this tag
|
|
for _, item := range tag {
|
|
n += len(item) // actual tag item
|
|
}
|
|
}
|
|
|
|
// content length and actual content
|
|
n += 2 + len(evt.Content)
|
|
|
|
return n
|
|
}
|
|
|
|
func Marshal(evt nostr.Event, buf []byte) error {
|
|
buf[0] = 0
|
|
|
|
if evt.Kind > MaxKind {
|
|
return fmt.Errorf("kind is too big: %d, max is %d", evt.Kind, MaxKind)
|
|
}
|
|
binary.LittleEndian.PutUint16(buf[1:3], uint16(evt.Kind))
|
|
|
|
if evt.CreatedAt > MaxCreatedAt {
|
|
return fmt.Errorf("created_at is too big: %d, max is %d", evt.CreatedAt, MaxCreatedAt)
|
|
}
|
|
binary.LittleEndian.PutUint32(buf[3:7], uint32(evt.CreatedAt))
|
|
|
|
hex.Decode(buf[7:39], []byte(evt.ID))
|
|
hex.Decode(buf[39:71], []byte(evt.PubKey))
|
|
hex.Decode(buf[71:135], []byte(evt.Sig))
|
|
|
|
tagBase := 135
|
|
// buf[135:137] (tagsSectionLength) will be set later when we know the absolute size of the tags section
|
|
|
|
ntags := len(evt.Tags)
|
|
if ntags > MaxTagCount {
|
|
return fmt.Errorf("can't encode too many tags: %d, max is %d", ntags, MaxTagCount)
|
|
}
|
|
binary.LittleEndian.PutUint16(buf[137:139], uint16(ntags))
|
|
|
|
tagOffset := 2 + 2 + ntags*2
|
|
for t, tag := range evt.Tags {
|
|
binary.LittleEndian.PutUint16(buf[tagBase+2+2+t*2:], uint16(tagOffset))
|
|
|
|
itemCount := len(tag)
|
|
if itemCount > MaxTagItemCount {
|
|
return fmt.Errorf("can't encode a tag with so many items: %d, max is %d", itemCount, MaxTagItemCount)
|
|
}
|
|
buf[tagBase+tagOffset] = uint8(itemCount)
|
|
|
|
itemOffset := 1
|
|
for _, item := range tag {
|
|
itemSize := len(item)
|
|
if itemSize > MaxTagItemSize {
|
|
return fmt.Errorf("tag item is too large: %d, max is %d", itemSize, MaxTagItemSize)
|
|
}
|
|
|
|
binary.LittleEndian.PutUint16(buf[tagBase+tagOffset+itemOffset:], uint16(itemSize))
|
|
copy(buf[tagBase+tagOffset+itemOffset+2:], []byte(item))
|
|
itemOffset += 2 + len(item)
|
|
}
|
|
tagOffset += itemOffset
|
|
}
|
|
|
|
tagsSectionLength := tagOffset
|
|
binary.LittleEndian.PutUint16(buf[tagBase:], uint16(tagsSectionLength))
|
|
|
|
// content
|
|
if contentLength := len(evt.Content); contentLength > MaxContentSize {
|
|
return fmt.Errorf("content is too large: %d, max is %d", contentLength, MaxContentSize)
|
|
} else {
|
|
binary.LittleEndian.PutUint16(buf[tagBase+tagsSectionLength:], uint16(contentLength))
|
|
}
|
|
copy(buf[tagBase+tagsSectionLength+2:], []byte(evt.Content))
|
|
|
|
return nil
|
|
}
|
|
|
|
func Unmarshal(data []byte, evt *nostr.Event) (err error) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = fmt.Errorf("failed to decode binary: %v", r)
|
|
}
|
|
}()
|
|
|
|
evt.Kind = int(binary.LittleEndian.Uint16(data[1:3]))
|
|
evt.CreatedAt = nostr.Timestamp(binary.LittleEndian.Uint32(data[3:7]))
|
|
evt.ID = hex.EncodeToString(data[7:39])
|
|
evt.PubKey = hex.EncodeToString(data[39:71])
|
|
evt.Sig = hex.EncodeToString(data[71:135])
|
|
|
|
const tagbase = 135
|
|
tagsSectionLength := binary.LittleEndian.Uint16(data[tagbase:])
|
|
ntags := binary.LittleEndian.Uint16(data[tagbase+2:])
|
|
evt.Tags = make(nostr.Tags, ntags)
|
|
for t := range evt.Tags {
|
|
offset := binary.LittleEndian.Uint16(data[tagbase+4+t*2:])
|
|
nitems := int(data[tagbase+offset])
|
|
tag := make(nostr.Tag, nitems)
|
|
|
|
curr := tagbase + offset + 1
|
|
for i := range tag {
|
|
length := binary.LittleEndian.Uint16(data[curr:])
|
|
tag[i] = string(data[curr+2 : curr+2+length])
|
|
curr += 2 + length
|
|
}
|
|
evt.Tags[t] = tag
|
|
}
|
|
|
|
contentLength := binary.LittleEndian.Uint16(data[tagbase+tagsSectionLength:])
|
|
evt.Content = string(data[tagbase+tagsSectionLength+2 : tagbase+tagsSectionLength+2+contentLength])
|
|
|
|
return err
|
|
}
|