This commit is contained in:
Sudo-Ivan
2024-12-31 14:00:10 -06:00
parent 28d46921d3
commit 8a175e3051
5 changed files with 7 additions and 882 deletions

View File

@@ -1,266 +0,0 @@
package main
import (
"flag"
"fmt"
"io"
"log"
"os"
"os/signal"
"path/filepath"
"syscall"
"time"
"github.com/Sudo-Ivan/reticulum-go/internal/config"
"github.com/Sudo-Ivan/reticulum-go/pkg/common"
"github.com/Sudo-Ivan/reticulum-go/pkg/destination"
"github.com/Sudo-Ivan/reticulum-go/pkg/identity"
"github.com/Sudo-Ivan/reticulum-go/pkg/link"
"github.com/Sudo-Ivan/reticulum-go/pkg/packet"
"github.com/Sudo-Ivan/reticulum-go/pkg/resource"
"github.com/Sudo-Ivan/reticulum-go/pkg/transport"
)
const (
APP_NAME = "example_utilities"
APP_ASPECT = "filetransfer"
)
var (
configPath = flag.String("config", "", "Path to config file")
servePath = flag.String("serve", "", "Directory to serve files from")
)
type FileServer struct {
config *common.ReticulumConfig
transport *transport.Transport
interfaces []common.NetworkInterface
identity *identity.Identity
servePath string
}
func NewFileServer(cfg *common.ReticulumConfig, servePath string) (*FileServer, error) {
if cfg == nil {
var err error
cfg, err = config.InitConfig()
if err != nil {
return nil, fmt.Errorf("failed to initialize config: %v", err)
}
}
t, err := transport.NewTransport(cfg)
if err != nil {
return nil, fmt.Errorf("failed to initialize transport: %v", err)
}
id, err := identity.New()
if err != nil {
return nil, fmt.Errorf("failed to create identity: %v", err)
}
return &FileServer{
config: cfg,
transport: t,
interfaces: make([]common.NetworkInterface, 0),
identity: id,
servePath: servePath,
}, nil
}
func (s *FileServer) OnLinkEstablished(l *link.Link) {
s.handleLinkEstablished(l)
}
func (s *FileServer) Start() error {
dest, err := destination.New(
s.identity,
destination.OUT,
destination.SINGLE,
APP_NAME,
APP_ASPECT,
)
if err != nil {
return fmt.Errorf("failed to create destination: %v", err)
}
callback := func(l interface{}) {
if link, ok := l.(*link.Link); ok {
s.OnLinkEstablished(link)
}
}
dest.SetLinkEstablishedCallback(callback)
log.Printf("File server started. Server hash: %s", s.identity.Hex())
log.Printf("Serving directory: %s", s.servePath)
return nil
}
func (s *FileServer) handleLinkEstablished(l *link.Link) {
log.Printf("Client connected")
l.SetPacketCallback(func(data []byte, p *packet.Packet) {
s.handlePacket(data, l)
})
l.SetResourceCallback(func(r interface{}) bool {
if res, ok := r.(*resource.Resource); ok {
return s.handleResource(res)
}
return false
})
}
func (s *FileServer) handlePacket(data []byte, l *link.Link) {
if string(data) == "LIST" {
files, err := s.getFileList()
if err != nil {
log.Printf("Error getting file list: %v", err)
l.Teardown()
return
}
if err := l.SendPacket(files); err != nil {
log.Printf("Error sending file list: %v", err)
l.Teardown()
}
}
}
func (s *FileServer) handleResource(r *resource.Resource) bool {
filename := filepath.Join(s.servePath, r.GetName())
file, err := os.Create(filename)
if err != nil {
log.Printf("Failed to create file: %v", err)
return false
}
defer file.Close()
written, err := io.Copy(file, r)
if err != nil {
log.Printf("Failed to write file: %v", err)
return false
}
log.Printf("Received file: %s (%d bytes)", filename, written)
return true
}
func (s *FileServer) getFileList() ([]byte, error) {
files, err := os.ReadDir(s.servePath)
if err != nil {
return nil, err
}
var fileList []string
for _, file := range files {
if !file.IsDir() {
fileList = append(fileList, file.Name())
}
}
return []byte(fmt.Sprintf("%v", fileList)), nil
}
func main() {
flag.Parse()
if *servePath == "" {
log.Fatal("Please specify a directory to serve with -serve")
}
var cfg *common.ReticulumConfig
var err error
if *configPath == "" {
cfg, err = config.InitConfig()
} else {
cfg, err = config.LoadConfig(*configPath)
}
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
server, err := NewFileServer(cfg, *servePath)
if err != nil {
log.Fatalf("Failed to create server: %v", err)
}
if err := server.Start(); err != nil {
log.Fatalf("Failed to start server: %v", err)
}
// Start watching the directory for changes
go server.watchDirectory()
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
}
func (s *FileServer) watchDirectory() {
for {
time.Sleep(1 * time.Second)
files, err := os.ReadDir(s.servePath)
if err != nil {
log.Printf("Error reading directory: %v", err)
continue
}
for _, file := range files {
if !file.IsDir() {
// Try to send file to connected peers
filePath := filepath.Join(s.servePath, file.Name())
if err := s.sendFile(filePath); err != nil {
log.Printf("Error sending file %s: %v", file.Name(), err)
}
}
}
}
}
func (s *FileServer) sendFile(filePath string) error {
file, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("failed to open file: %v", err)
}
defer file.Close()
// Create a destination for the file transfer
dest, err := destination.New(
s.identity,
destination.OUT,
destination.SINGLE,
APP_NAME,
APP_ASPECT,
)
if err != nil {
return fmt.Errorf("failed to create destination: %v", err)
}
// Set up link for file transfer
callback := func(l interface{}) {
if link, ok := l.(*link.Link); ok {
// Create a new resource with auto-compression enabled
res, err := resource.New(file, true)
if err != nil {
log.Printf("Error creating resource: %v", err)
return
}
// The filename is automatically set from the file handle
// in resource.New when using an io.ReadWriteSeeker
// Send the resource through the link
if err := link.SendResource(res); err != nil {
log.Printf("Error sending resource: %v", err)
return
}
log.Printf("File %s sent successfully", filepath.Base(filePath))
}
}
dest.SetLinkEstablishedCallback(callback)
return nil
}

View File

@@ -1,499 +0,0 @@
package main
import (
"bufio"
"encoding/binary"
"flag"
"fmt"
"io"
"log"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/Sudo-Ivan/reticulum-go/internal/config"
"github.com/Sudo-Ivan/reticulum-go/pkg/announce"
"github.com/Sudo-Ivan/reticulum-go/pkg/common"
"github.com/Sudo-Ivan/reticulum-go/pkg/destination"
"github.com/Sudo-Ivan/reticulum-go/pkg/identity"
"github.com/Sudo-Ivan/reticulum-go/pkg/interfaces"
"github.com/Sudo-Ivan/reticulum-go/pkg/link"
"github.com/Sudo-Ivan/reticulum-go/pkg/packet"
"github.com/Sudo-Ivan/reticulum-go/pkg/transport"
)
var (
configPath = flag.String("config", "", "Path to config file")
targetHash = flag.String("target", "", "Target destination hash")
generateIdentity = flag.Bool("generate-identity", false, "Generate a new identity and print its hash")
)
type Client struct {
config *common.ReticulumConfig
transport *transport.Transport
interfaces []common.NetworkInterface
identity *identity.Identity
}
func NewClient(cfg *common.ReticulumConfig) (*Client, error) {
if cfg == nil {
var err error
cfg, err = config.InitConfig()
if err != nil {
return nil, fmt.Errorf("failed to initialize config: %v", err)
}
}
t, err := transport.NewTransport(cfg)
if err != nil {
return nil, fmt.Errorf("failed to initialize transport: %v", err)
}
id, err := identity.New()
if err != nil {
return nil, fmt.Errorf("failed to create identity: %v", err)
}
return &Client{
config: cfg,
transport: t,
interfaces: make([]common.NetworkInterface, 0),
identity: id,
}, nil
}
func (c *Client) Start() error {
log.Printf("Starting Reticulum client...")
log.Printf("Configuration: %+v", c.config)
// Initialize transport
t, err := transport.NewTransport(c.config)
if err != nil {
return fmt.Errorf("failed to initialize transport: %v", err)
}
c.transport = t
log.Printf("Transport initialized")
log.Printf("Initializing network interfaces...")
for name, ifaceConfig := range c.config.Interfaces {
if !ifaceConfig.Enabled {
log.Printf("Skipping disabled interface %s", name)
continue
}
log.Printf("Configuring interface %s (%s)", name, ifaceConfig.Type)
var iface common.NetworkInterface
switch ifaceConfig.Type {
case "TCPClientInterface":
log.Printf("Connecting to %s:%d via TCP...", ifaceConfig.TargetHost, ifaceConfig.TargetPort)
client, err := interfaces.NewTCPClient(
name,
ifaceConfig.TargetHost,
ifaceConfig.TargetPort,
ifaceConfig.KISSFraming,
ifaceConfig.I2PTunneled,
ifaceConfig.Enabled,
)
if err != nil {
return fmt.Errorf("failed to create TCP interface %s: %v", name, err)
}
if err := client.Start(); err != nil {
return fmt.Errorf("failed to start TCP interface %s: %v", name, err)
}
iface = client
log.Printf("Successfully connected to %s:%d", ifaceConfig.TargetHost, ifaceConfig.TargetPort)
case "UDPInterface":
addr := fmt.Sprintf("%s:%d", ifaceConfig.Address, ifaceConfig.Port)
target := fmt.Sprintf("%s:%d", ifaceConfig.TargetHost, ifaceConfig.TargetPort)
log.Printf("Starting UDP interface on %s...", addr)
udp, err := interfaces.NewUDPInterface(
name,
addr,
target,
ifaceConfig.Enabled,
)
if err != nil {
return fmt.Errorf("failed to create UDP interface %s: %v", name, err)
}
if err := udp.Start(); err != nil {
return fmt.Errorf("failed to start UDP interface %s: %v", name, err)
}
iface = udp
log.Printf("UDP interface listening on %s", addr)
}
if iface != nil {
// Set packet callback
iface.SetPacketCallback(c.transport.HandlePacket)
c.interfaces = append(c.interfaces, iface)
log.Printf("Created and started interface %s (type=%v, enabled=%v)",
name, iface.GetType(), iface.IsEnabled())
}
}
// Register announce handler with explicit type
var handler transport.AnnounceHandler = &ClientAnnounceHandler{client: c}
c.transport.RegisterAnnounceHandler(handler)
// Send initial announce
log.Printf("Sending initial announce...")
if err := c.sendAnnounce(); err != nil {
log.Printf("Warning: Failed to send initial announce: %v", err)
}
return nil
}
func (c *Client) handlePacket(data []byte, p *packet.Packet) {
if len(data) < 1 {
return
}
header := data[0]
packetType := header & 0x03 // Extract packet type from header
switch packetType {
case announce.PACKET_TYPE_ANNOUNCE:
log.Printf("Received announce packet:")
log.Printf(" Raw data: %x", data)
// Create announce instance
a, err := announce.New(c.identity, []byte("RNS.Go.Client"), false)
if err != nil {
log.Printf("Failed to create announce handler: %v", err)
return
}
// Handle the announce
if err := a.HandleAnnounce(data[1:]); err != nil {
log.Printf("Failed to handle announce: %v", err)
}
default:
c.transport.HandlePacket(data, p)
}
}
func (c *Client) handleAnnounce(data []byte) {
if len(data) < 42 {
log.Printf("Received malformed announce packet (too short)")
return
}
destHash := data[:32]
timestamp := binary.BigEndian.Uint64(data[32:40])
hops := data[40]
flags := data[41]
log.Printf("Received announce from %x", destHash)
log.Printf(" Timestamp: %d", timestamp)
log.Printf(" Hops: %d", hops)
log.Printf(" Flags: %x", flags)
// Extract public key if present (after flags)
if len(data) > 42 {
pubKeyLen := 32 // Ed25519 public key length
pubKey := data[42 : 42+pubKeyLen]
log.Printf(" Public Key: %x", pubKey)
// Extract app data if present
var appData []byte
if len(data) > 42+pubKeyLen+2 {
dataLen := binary.BigEndian.Uint16(data[42+pubKeyLen : 42+pubKeyLen+2])
if len(data) >= 42+pubKeyLen+2+int(dataLen) {
appData = data[42+pubKeyLen+2 : 42+pubKeyLen+2+int(dataLen)]
log.Printf(" App Data: %s", string(appData))
}
}
// Store the identity for future use with all required parameters
if !identity.ValidateAnnounce(data, destHash, pubKey, data[len(data)-64:], appData) {
log.Printf("Failed to validate announce")
return
}
log.Printf("Successfully validated and stored announce")
}
}
func (c *Client) sendAnnounce() error {
// Create announce packet
identityHash := c.identity.Hash()
announceData := make([]byte, 0)
// Add header
header := []byte{0x01, 0x00} // Announce packet type
announceData = append(announceData, header...)
// Add destination hash
announceData = append(announceData, identityHash...)
// Add context byte
announceData = append(announceData, announce.ANNOUNCE_IDENTITY)
// Add public key
announceData = append(announceData, c.identity.GetPublicKey()...)
// App data with length prefix
appData := []byte("RNS.Go.Client")
lenBytes := make([]byte, 2)
binary.BigEndian.PutUint16(lenBytes, uint16(len(appData)))
announceData = append(announceData, lenBytes...)
announceData = append(announceData, appData...)
// Add signature
signData := append(identityHash, c.identity.GetPublicKey()...)
signData = append(signData, appData...)
signature := c.identity.Sign(signData)
announceData = append(announceData, signature...)
log.Printf("Sending announce:")
log.Printf(" Identity Hash: %x", identityHash)
log.Printf(" Packet Length: %d bytes", len(announceData))
log.Printf(" Full Packet: %x", announceData)
sentCount := 0
// Send on all interfaces
for _, iface := range c.interfaces {
log.Printf("Attempting to send on interface %s:", iface.GetName())
log.Printf(" Type: %v", iface.GetType())
log.Printf(" MTU: %d bytes", iface.GetMTU())
log.Printf(" Status: enabled=%v", iface.IsEnabled())
if !iface.IsEnabled() {
log.Printf(" Skipping disabled interface")
continue
}
if err := iface.Send(announceData, ""); err != nil {
log.Printf(" Failed to send: %v", err)
} else {
log.Printf(" Successfully sent announce")
sentCount++
}
}
if sentCount == 0 {
return fmt.Errorf("no interfaces available to send announce")
}
return nil
}
func (c *Client) Stop() {
for _, iface := range c.interfaces {
iface.Detach()
}
c.transport.Close()
}
func (c *Client) Connect(destHash []byte) error {
// Recall server identity
serverIdentity, err := identity.Recall(destHash)
if err != nil {
return err
}
// Create destination
dest, err := destination.New(
serverIdentity,
destination.OUT,
destination.SINGLE,
"example_utilities",
"identifyexample",
)
if err != nil {
return err
}
// Create link with all required parameters
link := link.NewLink(
dest,
c.transport, // Add the transport instance
c.handleLinkEstablished,
c.handleLinkClosed,
)
// Set callbacks
link.SetPacketCallback(c.handlePacket)
return nil
}
func (c *Client) handleLinkEstablished(l *link.Link) {
log.Printf("Link established with server, identifying...")
// Identify to server
if err := l.Identify(c.identity); err != nil {
log.Printf("Failed to identify: %v", err)
l.Teardown()
return
}
}
func (c *Client) handleLinkClosed(l *link.Link) {
log.Printf("Link closed")
}
type ClientAnnounceHandler struct {
client *Client
}
func (h *ClientAnnounceHandler) AspectFilter() []string {
return []string{"RNS.Go.Client"}
}
func (h *ClientAnnounceHandler) ReceivedAnnounce(destinationHash []byte, announcedIdentity interface{}, appData []byte) error {
log.Printf("=== Received Announce Details ===")
log.Printf("Destination Hash: %x", destinationHash)
log.Printf("App Data: %s", string(appData))
// Type assert the identity
if id, ok := announcedIdentity.(*identity.Identity); ok {
log.Printf("Identity Public Key: %x", id.GetPublicKey())
// Create packet hash for storage
packetHash := identity.TruncatedHash(append(destinationHash, id.GetPublicKey()...))
log.Printf("Generated Packet Hash: %x", packetHash)
// Store the peer identity with all required parameters
identity.Remember(packetHash, destinationHash, id.GetPublicKey(), appData)
log.Printf("Identity stored successfully")
log.Printf("===========================")
return nil
}
log.Printf("Error: Invalid identity type")
log.Printf("===========================")
return fmt.Errorf("invalid identity type")
}
func (h *ClientAnnounceHandler) ReceivePathResponses() bool {
return true
}
func main() {
flag.Parse()
log.Printf("Starting Reticulum Go client...")
log.Printf("Config path: %s", *configPath)
log.Printf("Target hash: %s", *targetHash)
var cfg *common.ReticulumConfig
var err error
if *configPath == "" {
log.Printf("No config path specified, using default configuration")
cfg, err = config.InitConfig()
} else {
log.Printf("Loading configuration from: %s", *configPath)
cfg, err = config.LoadConfig(*configPath)
}
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
log.Printf("Configuration loaded successfully")
if *generateIdentity {
log.Printf("Generating new identity...")
id, err := identity.New()
if err != nil {
log.Fatalf("Failed to generate identity: %v", err)
}
fmt.Printf("Identity hash: %s\n", id.Hex())
return
}
client, err := NewClient(cfg)
if err != nil {
log.Fatalf("Failed to create client: %v", err)
}
defer client.Stop()
if err := client.Start(); err != nil {
log.Fatalf("Failed to start client: %v", err)
}
log.Printf("Client running, press Ctrl+C to exit")
// If target is specified, start interactive mode
if *targetHash != "" {
targetBytes, err := identity.HashFromString(*targetHash)
if err != nil {
log.Fatalf("Invalid target hash: %v", err)
}
link, err := client.transport.GetLink(targetBytes)
if err != nil {
log.Fatalf("Failed to get link: %v", err)
}
log.Printf("Starting interactive mode...")
interactiveLoop(link)
return
}
// Wait for interrupt if no target specified
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
log.Printf("Received interrupt signal, shutting down...")
}
func interactiveLoop(link *transport.Link) {
reader := bufio.NewReader(os.Stdin)
connected := make(chan struct{})
disconnected := make(chan struct{})
// Set up connection status handlers
link.OnConnected(func() {
connected <- struct{}{}
})
link.OnDisconnected(func() {
disconnected <- struct{}{}
})
// Wait for initial connection
select {
case <-connected:
log.Println("Connected to target")
case <-time.After(10 * time.Second):
log.Fatal("Connection timeout")
return
}
// Start input loop
for {
select {
case <-disconnected:
log.Println("Connection lost")
return
default:
fmt.Print("> ")
input, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
return
}
log.Printf("Error reading input: %v", err)
continue
}
input = strings.TrimSpace(input)
if input == "quit" || input == "exit" {
return
}
if err := link.Send([]byte(input)); err != nil {
log.Printf("Failed to send: %v", err)
return
}
}
}
}