Files
Reticulum-Go/pkg/destination/destination.go
2024-12-30 23:41:18 -06:00

368 lines
8.6 KiB
Go

package destination
import (
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"sync"
"github.com/Sudo-Ivan/reticulum-go/pkg/common"
"github.com/Sudo-Ivan/reticulum-go/pkg/identity"
"github.com/Sudo-Ivan/reticulum-go/pkg/transport"
)
const (
IN = 0x01
OUT = 0x02
SINGLE = 0x00
GROUP = 0x01
PLAIN = 0x02
PROVE_NONE = 0x00
PROVE_ALL = 0x01
PROVE_APP = 0x02
ALLOW_NONE = 0x00
ALLOW_ALL = 0x01
ALLOW_LIST = 0x02
RATCHET_COUNT = 512 // Default number of retained ratchet keys
RATCHET_INTERVAL = 1800 // Minimum interval between ratchet rotations in seconds
)
type PacketCallback = common.PacketCallback
type ProofRequestedCallback = common.ProofRequestedCallback
type LinkEstablishedCallback = common.LinkEstablishedCallback
type RequestHandler struct {
Path string
ResponseGenerator func(path string, data []byte, requestID []byte, linkID []byte, remoteIdentity *identity.Identity, requestedAt int64) []byte
AllowMode byte
AllowedList [][]byte
}
type Destination struct {
identity *identity.Identity
direction byte
destType byte
appName string
aspects []string
hash []byte
acceptsLinks bool
proofStrategy byte
packetCallback PacketCallback
proofCallback ProofRequestedCallback
linkCallback LinkEstablishedCallback
ratchetsEnabled bool
ratchetPath string
ratchetCount int
ratchetInterval int
enforceRatchets bool
defaultAppData []byte
mutex sync.RWMutex
requestHandlers map[string]*RequestHandler
callbacks struct {
packetReceived common.PacketCallback
proofRequested common.ProofRequestedCallback
linkEstablished common.LinkEstablishedCallback
}
}
func New(id *identity.Identity, direction byte, destType byte, appName string, aspects ...string) (*Destination, error) {
if id == nil {
return nil, errors.New("identity cannot be nil")
}
d := &Destination{
identity: id,
direction: direction,
destType: destType,
appName: appName,
aspects: aspects,
acceptsLinks: false,
proofStrategy: PROVE_NONE,
ratchetCount: RATCHET_COUNT,
ratchetInterval: RATCHET_INTERVAL,
requestHandlers: make(map[string]*RequestHandler),
}
// Generate destination hash
d.hash = d.Hash()
return d, nil
}
func (d *Destination) Hash() []byte {
nameHash := sha256.Sum256([]byte(d.ExpandName()))
identityHash := sha256.Sum256(d.identity.GetPublicKey())
combined := append(nameHash[:], identityHash[:]...)
finalHash := sha256.Sum256(combined)
return finalHash[:16] // Truncated to 128 bits
}
func (d *Destination) ExpandName() string {
name := d.appName
for _, aspect := range d.aspects {
name += "." + aspect
}
return name
}
func (d *Destination) Announce(appData []byte) error {
d.mutex.Lock()
defer d.mutex.Unlock()
// If no specific appData provided, use default
if appData == nil {
appData = d.defaultAppData
}
// Create announce packet
packet := make([]byte, 0)
// Add destination hash
packet = append(packet, d.hash...)
// Add identity public key
packet = append(packet, d.identity.GetPublicKey()...)
// Add flags byte
flags := byte(0)
if d.acceptsLinks {
flags |= 0x01
}
if d.ratchetsEnabled {
flags |= 0x02
}
packet = append(packet, flags)
// Add proof strategy
packet = append(packet, d.proofStrategy)
// Add app data length and data if present
if appData != nil {
appDataLen := uint16(len(appData))
lenBytes := make([]byte, 2)
binary.BigEndian.PutUint16(lenBytes, appDataLen)
packet = append(packet, lenBytes...)
packet = append(packet, appData...)
} else {
// No app data
packet = append(packet, 0x00, 0x00)
}
// Add ratchet data if enabled
if d.ratchetsEnabled {
// Add ratchet interval
intervalBytes := make([]byte, 4)
binary.BigEndian.PutUint32(intervalBytes, uint32(d.ratchetInterval))
packet = append(packet, intervalBytes...)
// Add current ratchet key
ratchetKey := d.identity.GetCurrentRatchetKey()
if ratchetKey == nil {
return errors.New("failed to get current ratchet key")
}
packet = append(packet, ratchetKey...)
}
// Sign the announce packet
signature, err := d.Sign(packet)
if err != nil {
return fmt.Errorf("failed to sign announce packet: %w", err)
}
packet = append(packet, signature...)
// Send announce packet through transport layer
// This will need to be implemented in the transport package
return transport.SendAnnounce(packet)
}
func (d *Destination) AcceptsLinks(accepts bool) {
d.mutex.Lock()
defer d.mutex.Unlock()
d.acceptsLinks = accepts
}
func (d *Destination) SetLinkEstablishedCallback(callback common.LinkEstablishedCallback) {
d.mutex.Lock()
defer d.mutex.Unlock()
d.linkCallback = callback
}
func (d *Destination) SetPacketCallback(callback common.PacketCallback) {
d.mutex.Lock()
defer d.mutex.Unlock()
d.packetCallback = callback
}
func (d *Destination) SetProofRequestedCallback(callback common.ProofRequestedCallback) {
d.mutex.Lock()
defer d.mutex.Unlock()
d.proofCallback = callback
}
func (d *Destination) SetProofStrategy(strategy byte) {
d.mutex.Lock()
defer d.mutex.Unlock()
d.proofStrategy = strategy
}
func (d *Destination) EnableRatchets(path string) bool {
d.mutex.Lock()
defer d.mutex.Unlock()
d.ratchetsEnabled = true
d.ratchetPath = path
return true
}
func (d *Destination) EnforceRatchets() {
d.mutex.Lock()
defer d.mutex.Unlock()
d.enforceRatchets = true
}
func (d *Destination) SetRetainedRatchets(count int) bool {
if count < 1 {
return false
}
d.mutex.Lock()
defer d.mutex.Unlock()
d.ratchetCount = count
return true
}
func (d *Destination) SetRatchetInterval(interval int) bool {
if interval < 1 {
return false
}
d.mutex.Lock()
defer d.mutex.Unlock()
d.ratchetInterval = interval
return true
}
func (d *Destination) SetDefaultAppData(data []byte) {
d.mutex.Lock()
defer d.mutex.Unlock()
d.defaultAppData = data
}
func (d *Destination) ClearDefaultAppData() {
d.mutex.Lock()
defer d.mutex.Unlock()
d.defaultAppData = nil
}
func (d *Destination) RegisterRequestHandler(path string, responseGen func(string, []byte, []byte, []byte, *identity.Identity, int64) []byte, allow byte, allowedList [][]byte) error {
if path == "" {
return errors.New("path cannot be empty")
}
if allow != ALLOW_NONE && allow != ALLOW_ALL && allow != ALLOW_LIST {
return errors.New("invalid allow mode")
}
if allow == ALLOW_LIST && (allowedList == nil || len(allowedList) == 0) {
return errors.New("allowed list required for ALLOW_LIST mode")
}
d.mutex.Lock()
defer d.mutex.Unlock()
d.requestHandlers[path] = &RequestHandler{
Path: path,
ResponseGenerator: responseGen,
AllowMode: allow,
AllowedList: allowedList,
}
return nil
}
func (d *Destination) DeregisterRequestHandler(path string) bool {
d.mutex.Lock()
defer d.mutex.Unlock()
if _, exists := d.requestHandlers[path]; exists {
delete(d.requestHandlers, path)
return true
}
return false
}
func (d *Destination) Encrypt(plaintext []byte) ([]byte, error) {
if d.destType == PLAIN {
return plaintext, nil
}
if d.identity == nil {
return nil, errors.New("no identity available for encryption")
}
switch d.destType {
case SINGLE:
// For single destination, we need the recipient's public key
recipientKey := d.identity.GetPublicKey()
return d.identity.Encrypt(plaintext, recipientKey)
case GROUP:
key := d.identity.GetCurrentRatchetKey()
if key == nil {
return nil, errors.New("no ratchet key available")
}
return d.identity.EncryptSymmetric(plaintext, key)
default:
return nil, errors.New("unsupported destination type for encryption")
}
}
func (d *Destination) Decrypt(ciphertext []byte) ([]byte, error) {
if d.destType == PLAIN {
return ciphertext, nil
}
if d.identity == nil {
return nil, errors.New("no identity available for decryption")
}
// Create empty ratchet receiver to get latest ratchet ID if available
ratchetReceiver := &common.RatchetIDReceiver{}
// Call Decrypt with full parameter list:
// - ciphertext: the encrypted data
// - ratchets: nil since we're not providing specific ratchets
// - enforceRatchets: false to allow fallback to normal decryption
// - ratchetIDReceiver: to receive the latest ratchet ID used
return d.identity.Decrypt(ciphertext, nil, false, ratchetReceiver)
}
func (d *Destination) Sign(data []byte) ([]byte, error) {
if d.identity == nil {
return nil, errors.New("no identity available")
}
signature := d.identity.Sign(data)
return signature, nil
}
func (d *Destination) GetPublicKey() []byte {
if d.identity == nil {
return nil
}
return d.identity.GetPublicKey()
}
func (d *Destination) GetIdentity() *identity.Identity {
return d.identity
}