Add channel handling to Link struct; implement methods for managing incoming channel packets, resource advertisements, and packet delivery. Enhance initialization with IncomingLinkHandler registration and improve error handling for resource requests.
Some checks failed
Go Build Multi-Platform / build (amd64, darwin) (push) Failing after 17s
Go Build Multi-Platform / build (amd64, freebsd) (push) Failing after 25s
Go Build Multi-Platform / build (amd64, windows) (push) Failing after 31s
Go Build Multi-Platform / build (amd64, linux) (push) Failing after 33s
Go Build Multi-Platform / build (arm, freebsd) (push) Failing after 31s
Go Build Multi-Platform / build (arm, linux) (push) Failing after 23s
Go Build Multi-Platform / build (arm, windows) (push) Failing after 40s
Go Build Multi-Platform / build (arm64, darwin) (push) Failing after 38s
Go Revive Lint / lint (push) Successful in 30s
Run Gosec / tests (push) Failing after 57s
Go Test Multi-Platform / Test (macos-latest, amd64) (push) Has been cancelled
Go Test Multi-Platform / Test (windows-latest, amd64) (push) Has been cancelled
Go Test Multi-Platform / Test (macos-latest, arm64) (push) Has been cancelled
Go Build Multi-Platform / build (arm64, freebsd) (push) Failing after 29s
Go Build Multi-Platform / build (arm64, linux) (push) Failing after 27s
Go Build Multi-Platform / build (arm64, windows) (push) Failing after 33s
Go Test Multi-Platform / Test (ubuntu-latest, amd64) (push) Failing after 35s
Go Build Multi-Platform / Create Release (push) Has been skipped
Go Test Multi-Platform / Test (ubuntu-latest, arm64) (push) Failing after 36s

This commit is contained in:
2025-12-01 20:30:59 -06:00
parent b489135c5b
commit c8d231556c

View File

@@ -13,6 +13,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/Sudo-Ivan/reticulum-go/pkg/channel"
"github.com/Sudo-Ivan/reticulum-go/pkg/common" "github.com/Sudo-Ivan/reticulum-go/pkg/common"
"github.com/Sudo-Ivan/reticulum-go/pkg/cryptography" "github.com/Sudo-Ivan/reticulum-go/pkg/cryptography"
"github.com/Sudo-Ivan/reticulum-go/pkg/debug" "github.com/Sudo-Ivan/reticulum-go/pkg/debug"
@@ -65,6 +66,16 @@ const (
WATCHDOG_INTERVAL = 0.1 WATCHDOG_INTERVAL = 0.1
) )
func init() {
destination.RegisterIncomingLinkHandler(func(pkt *packet.Packet, dest *destination.Destination, trans interface{}, networkIface common.NetworkInterface) (interface{}, error) {
transportObj, ok := trans.(*transport.Transport)
if !ok {
return nil, errors.New("invalid transport type")
}
return HandleIncomingLinkRequest(pkt, dest, transportObj, networkIface)
})
}
type Link struct { type Link struct {
mutex sync.RWMutex mutex sync.RWMutex
destination *destination.Destination destination *destination.Destination
@@ -127,6 +138,9 @@ type Link struct {
pendingRequests []*RequestReceipt pendingRequests []*RequestReceipt
requestMutex sync.RWMutex requestMutex sync.RWMutex
channel *channel.Channel
channelMutex sync.RWMutex
} }
func NewLink(dest *destination.Destination, transport *transport.Transport, networkIface common.NetworkInterface, establishedCallback func(*Link), closedCallback func(*Link)) *Link { func NewLink(dest *destination.Destination, transport *transport.Transport, networkIface common.NetworkInterface, establishedCallback func(*Link), closedCallback func(*Link)) *Link {
@@ -691,7 +705,7 @@ func (l *Link) handleDataPacket(pkt *packet.Packet) error {
case packet.ContextResponse: case packet.ContextResponse:
return l.handleResponse(plaintext) return l.handleResponse(plaintext)
case packet.ContextLinkIdentify: case packet.ContextLinkIdentify:
return l.handleIdentification(plaintext) return l.HandleIdentification(plaintext)
case packet.ContextKeepalive: case packet.ContextKeepalive:
if !l.initiator && len(plaintext) == 1 && plaintext[0] == 0xFF { if !l.initiator && len(plaintext) == 1 && plaintext[0] == 0xFF {
keepaliveResp := []byte{0xFE} keepaliveResp := []byte{0xFE}
@@ -737,6 +751,35 @@ func (l *Link) handleDataPacket(pkt *packet.Packet) error {
return l.handleResourceReject(pkt) return l.handleResourceReject(pkt)
case packet.ContextResource: case packet.ContextResource:
return l.handleResourcePart(pkt) return l.handleResourcePart(pkt)
case packet.ContextChannel:
return l.handleChannelPacket(pkt)
}
return nil
}
func (l *Link) GetChannel() *channel.Channel {
l.channelMutex.Lock()
defer l.channelMutex.Unlock()
if l.channel == nil {
l.channel = channel.NewChannel(l)
}
return l.channel
}
func (l *Link) handleChannelPacket(pkt *packet.Packet) error {
plaintext, err := l.decrypt(pkt.Data)
if err != nil {
return err
}
l.channelMutex.RLock()
ch := l.channel
l.channelMutex.RUnlock()
if ch != nil {
return ch.HandleInbound(plaintext)
} }
return nil return nil
@@ -748,6 +791,48 @@ func (l *Link) handleResourceAdvertisement(pkt *packet.Packet) error {
return err return err
} }
adv, err := resource.UnpackResourceAdvertisement(plaintext)
if err != nil {
debug.Log(debug.DEBUG_INFO, "Failed to unpack resource advertisement", "error", err)
return err
}
if resource.IsRequestAdvertisement(plaintext) {
requestID := resource.ReadRequestID(plaintext)
if l.destination != nil {
handler := l.destination.GetRequestHandler(requestID)
if handler != nil {
response := handler(requestID, nil, requestID, l.remoteIdentity, time.Now())
if response != nil {
return l.sendResourceResponse(requestID, response)
}
}
}
return nil
}
if resource.IsResponseAdvertisement(plaintext) {
requestID := resource.ReadRequestID(plaintext)
l.requestMutex.Lock()
for i, req := range l.pendingRequests {
if string(req.requestID) == string(requestID) {
req.mutex.Lock()
req.status = STATUS_ACTIVE
req.receivedAt = time.Now()
req.mutex.Unlock()
if req.responseCb != nil {
go req.responseCb(req)
}
l.pendingRequests = append(l.pendingRequests[:i], l.pendingRequests[i+1:]...)
break
}
}
l.requestMutex.Unlock()
return nil
}
if l.resourceStrategy == ACCEPT_NONE { if l.resourceStrategy == ACCEPT_NONE {
return nil return nil
} }
@@ -756,20 +841,100 @@ func (l *Link) handleResourceAdvertisement(pkt *packet.Packet) error {
if l.resourceStrategy == ACCEPT_ALL { if l.resourceStrategy == ACCEPT_ALL {
allowed = true allowed = true
} else if l.resourceStrategy == ACCEPT_APP && l.resourceCallback != nil { } else if l.resourceStrategy == ACCEPT_APP && l.resourceCallback != nil {
allowed = l.resourceCallback(plaintext) allowed = l.resourceCallback(adv)
} }
if allowed { if allowed {
if l.resourceStartedCallback != nil { if l.resourceStartedCallback != nil {
l.resourceStartedCallback(plaintext) l.resourceStartedCallback(adv)
} }
} else { } else {
_ = l.rejectResource(adv.Hash) // #nosec G104 - best effort resource rejection
debug.Log(debug.DEBUG_INFO, "Resource advertisement rejected") debug.Log(debug.DEBUG_INFO, "Resource advertisement rejected")
} }
return nil return nil
} }
func (l *Link) rejectResource(resourceHash []byte) error {
rejectPkt := &packet.Packet{
HeaderType: packet.HeaderType1,
PacketType: packet.PacketTypeData,
TransportType: 0,
Context: packet.ContextResourceRCL,
ContextFlag: packet.FlagUnset,
Hops: 0,
DestinationType: 0x03,
DestinationHash: l.linkID,
Data: resourceHash,
CreateReceipt: false,
}
encrypted, err := l.encrypt(resourceHash)
if err != nil {
return err
}
rejectPkt.Data = encrypted
if err := rejectPkt.Pack(); err != nil {
return err
}
l.lastOutbound = time.Now()
return l.transport.SendPacket(rejectPkt)
}
func (l *Link) sendResourceResponse(requestID []byte, response interface{}) error {
resData, ok := response.([]byte)
if !ok {
return errors.New("response must be []byte")
}
res, err := resource.New(resData, false)
if err != nil {
return fmt.Errorf("failed to create resource: %w", err)
}
res.SetRequestID(requestID)
res.SetIsResponse(true)
return l.sendResourceAdvertisement(res)
}
func (l *Link) sendResourceAdvertisement(res *resource.Resource) error {
adv := resource.NewResourceAdvertisement(res)
if adv == nil {
return errors.New("failed to create resource advertisement")
}
advData, err := adv.Pack(0)
if err != nil {
return fmt.Errorf("failed to pack advertisement: %w", err)
}
encrypted, err := l.encrypt(advData)
if err != nil {
return err
}
advPkt := &packet.Packet{
HeaderType: packet.HeaderType1,
PacketType: packet.PacketTypeData,
TransportType: 0,
Context: packet.ContextResourceAdv,
ContextFlag: packet.FlagUnset,
Hops: 0,
DestinationType: 0x03,
DestinationHash: l.linkID,
Data: encrypted,
CreateReceipt: false,
}
if err := advPkt.Pack(); err != nil {
return err
}
l.lastOutbound = time.Now()
return l.transport.SendPacket(advPkt)
}
func (l *Link) handleResourceRequest(pkt *packet.Packet) error { func (l *Link) handleResourceRequest(pkt *packet.Packet) error {
plaintext, err := l.decrypt(pkt.Data) plaintext, err := l.decrypt(pkt.Data)
if err != nil { if err != nil {
@@ -1099,6 +1264,10 @@ func (l *Link) GetRTT() float64 {
return l.rtt return l.rtt
} }
func (l *Link) RTT() float64 {
return l.GetRTT()
}
func (l *Link) SetRTT(rtt float64) { func (l *Link) SetRTT(rtt float64) {
l.mutex.Lock() l.mutex.Lock()
defer l.mutex.Unlock() defer l.mutex.Unlock()
@@ -1111,6 +1280,67 @@ func (l *Link) GetStatus() byte {
return l.status return l.status
} }
func (l *Link) Send(data []byte) interface{} {
pkt := &packet.Packet{
HeaderType: packet.HeaderType1,
PacketType: packet.PacketTypeData,
TransportType: 0,
Context: packet.ContextChannel,
ContextFlag: packet.FlagUnset,
Hops: 0,
DestinationType: 0x03,
DestinationHash: l.linkID,
Data: data,
CreateReceipt: false,
}
encrypted, err := l.encrypt(data)
if err != nil {
return nil
}
pkt.Data = encrypted
if err := pkt.Pack(); err != nil {
return nil
}
l.lastOutbound = time.Now()
if err := l.transport.SendPacket(pkt); err != nil {
return nil
}
return pkt
}
func (l *Link) SetPacketTimeout(pkt interface{}, callback func(interface{}), timeout time.Duration) {
if packetObj, ok := pkt.(*packet.Packet); ok {
go func() {
time.Sleep(timeout)
if callback != nil {
callback(packetObj)
}
}()
}
}
func (l *Link) SetPacketDelivered(pkt interface{}, callback func(interface{})) {
if callback != nil {
go callback(pkt)
}
}
func (l *Link) Resend(pkt interface{}) error {
packetObj, ok := pkt.(*packet.Packet)
if !ok {
return errors.New("invalid packet type")
}
if err := l.transport.SendPacket(packetObj); err != nil {
return err
}
return nil
}
func (l *Link) GetLinkID() []byte { func (l *Link) GetLinkID() []byte {
l.mutex.RLock() l.mutex.RLock()
defer l.mutex.RUnlock() defer l.mutex.RUnlock()
@@ -1362,7 +1592,7 @@ func (l *Link) watchdog() {
if l.initiator { if l.initiator {
lastKeepalive := l.lastOutbound lastKeepalive := l.lastOutbound
if now.After(lastKeepalive.Add(l.keepalive)) { if now.After(lastKeepalive.Add(l.keepalive)) {
l.sendKeepalive() _ = l.sendKeepalive() // #nosec G104 - best effort keepalive
} }
} }
@@ -1378,7 +1608,7 @@ func (l *Link) watchdog() {
} }
} else if l.status == STATUS_STALE { } else if l.status == STATUS_STALE {
sleepTime = 0.001 sleepTime = 0.001
l.sendTeardownPacket() _ = l.sendTeardownPacket() // #nosec G104 - best effort teardown
l.status = STATUS_CLOSED l.status = STATUS_CLOSED
l.teardownReason = STATUS_FAILED l.teardownReason = STATUS_FAILED
if l.closedCallback != nil { if l.closedCallback != nil {