// 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 }