Files
Reticulum-Go/pkg/resource/advertisement.go

256 lines
5.6 KiB
Go

// SPDX-License-Identifier: 0BSD
// Copyright (c) 2024-2026 Sudo-Ivan / Quad4.io
package resource
import (
"fmt"
"math"
"git.quad4.io/Networks/Reticulum-Go/pkg/common"
)
const (
OVERHEAD = 134
COLLISION_GUARD_SIZE = 2*WINDOW_MAX + 100
)
type ResourceAdvertisement struct {
TransferSize int64
DataSize int64
Parts int
Hash []byte
RandomHash []byte
OriginalHash []byte
Hashmap []byte
Compressed bool
Encrypted bool
Split bool
HasMetadata bool
SegmentIndex uint16
TotalSegments uint16
RequestID []byte
IsRequest bool
IsResponse bool
Flags byte
}
func NewResourceAdvertisement(res *Resource) *ResourceAdvertisement {
if res == nil {
return nil
}
flags := byte(0x00)
if res.HasMetadata() {
flags |= 0x20
}
if res.IsResponse() {
flags |= 0x10
}
if res.IsRequest() {
flags |= 0x08
}
res.mutex.RLock()
split := res.split
compressed := res.compressed
encrypted := res.encrypted
randomHash := res.randomHash
originalHash := res.originalHash
segmentIndex := res.segmentIndex
totalSegments := res.totalSegments
res.mutex.RUnlock()
if split {
flags |= 0x04
}
if compressed {
flags |= 0x02
}
if encrypted {
flags |= 0x01
}
hashmap := res.getHashmap()
return &ResourceAdvertisement{
TransferSize: res.GetTransferSize(),
DataSize: res.GetDataSize(),
Parts: int(res.GetSegments()),
Hash: res.GetHash(),
RandomHash: randomHash,
OriginalHash: originalHash,
Hashmap: hashmap,
Compressed: compressed,
Encrypted: encrypted,
Split: split,
HasMetadata: res.HasMetadata(),
SegmentIndex: segmentIndex,
TotalSegments: totalSegments,
RequestID: res.GetRequestID(),
IsRequest: res.IsRequest(),
IsResponse: res.IsResponse(),
Flags: flags,
}
}
func (ra *ResourceAdvertisement) Pack(segment int) ([]byte, error) {
hashmapMaxLen := getHashmapMaxLen()
hashmapStart := segment * hashmapMaxLen
hashmapEnd := hashmapStart + hashmapMaxLen
if hashmapEnd > len(ra.Hashmap)/MAPHASH_LEN {
hashmapEnd = len(ra.Hashmap) / MAPHASH_LEN
}
hashmap := ra.Hashmap[hashmapStart*MAPHASH_LEN : hashmapEnd*MAPHASH_LEN]
dict := map[string]interface{}{
"t": ra.TransferSize,
"d": ra.DataSize,
"n": ra.Parts,
"h": ra.Hash,
"r": ra.RandomHash,
"o": ra.OriginalHash,
"i": ra.SegmentIndex,
"l": ra.TotalSegments,
"q": ra.RequestID,
"f": ra.Flags,
"m": hashmap,
}
return common.MsgpackMarshal(dict)
}
func UnpackResourceAdvertisement(data []byte) (*ResourceAdvertisement, error) {
var dict map[string]interface{}
if err := common.MsgpackUnmarshal(data, &dict); err != nil {
return nil, fmt.Errorf("failed to unpack advertisement: %w", err)
}
ra := &ResourceAdvertisement{}
if t, ok := dict["t"].(int64); ok {
ra.TransferSize = t
} else if t, ok := dict["t"].(uint64); ok {
if t > uint64(math.MaxInt64) {
return nil, fmt.Errorf("transfer size overflow")
}
ra.TransferSize = int64(t) // #nosec G115 - checked for overflow
}
if d, ok := dict["d"].(int64); ok {
ra.DataSize = d
} else if d, ok := dict["d"].(uint64); ok {
if d > uint64(math.MaxInt64) {
return nil, fmt.Errorf("data size overflow")
}
ra.DataSize = int64(d) // #nosec G115 - checked for overflow
}
if n, ok := dict["n"].(int); ok {
ra.Parts = n
} else if n, ok := dict["n"].(uint64); ok {
if n > uint64(math.MaxInt32) {
return nil, fmt.Errorf("parts count overflow")
}
ra.Parts = int(n) // #nosec G115 - checked for overflow
}
if h, ok := dict["h"].([]byte); ok {
ra.Hash = h
}
if r, ok := dict["r"].([]byte); ok {
ra.RandomHash = r
}
if o, ok := dict["o"].([]byte); ok {
ra.OriginalHash = o
}
if m, ok := dict["m"].([]byte); ok {
ra.Hashmap = m
}
if f, ok := dict["f"].(byte); ok {
ra.Flags = f
} else if f, ok := dict["f"].(uint64); ok {
ra.Flags = byte(f)
}
ra.Encrypted = (ra.Flags & 0x01) == 0x01
ra.Compressed = ((ra.Flags >> 1) & 0x01) == 0x01
ra.Split = ((ra.Flags >> 2) & 0x01) == 0x01
ra.IsRequest = ((ra.Flags >> 3) & 0x01) == 0x01
ra.IsResponse = ((ra.Flags >> 4) & 0x01) == 0x01
ra.HasMetadata = ((ra.Flags >> 5) & 0x01) == 0x01
if i, ok := dict["i"].(uint16); ok {
ra.SegmentIndex = i
} else if i, ok := dict["i"].(uint64); ok {
if i > uint64(math.MaxUint16) {
return nil, fmt.Errorf("segment index overflow")
}
ra.SegmentIndex = uint16(i) // #nosec G115 - checked for overflow
}
if l, ok := dict["l"].(uint16); ok {
ra.TotalSegments = l
} else if l, ok := dict["l"].(uint64); ok {
if l > uint64(math.MaxUint16) {
return nil, fmt.Errorf("total segments overflow")
}
ra.TotalSegments = uint16(l) // #nosec G115 - checked for overflow
}
if q, ok := dict["q"].([]byte); ok {
ra.RequestID = q
}
return ra, nil
}
func getHashmapMaxLen() int {
mdu := 384
return (mdu - OVERHEAD) / MAPHASH_LEN
}
func IsRequestAdvertisement(data []byte) bool {
adv, err := UnpackResourceAdvertisement(data)
if err != nil {
return false
}
return adv.IsRequest && adv.RequestID != nil
}
func IsResponseAdvertisement(data []byte) bool {
adv, err := UnpackResourceAdvertisement(data)
if err != nil {
return false
}
return adv.IsResponse && adv.RequestID != nil
}
func ReadRequestID(data []byte) []byte {
adv, err := UnpackResourceAdvertisement(data)
if err != nil {
return nil
}
return adv.RequestID
}
func ReadTransferSize(data []byte) int64 {
adv, err := UnpackResourceAdvertisement(data)
if err != nil {
return 0
}
return adv.TransferSize
}
func ReadSize(data []byte) int64 {
adv, err := UnpackResourceAdvertisement(data)
if err != nil {
return 0
}
return adv.DataSize
}