refactor resolver

This commit is contained in:
eyedeekay
2024-12-09 14:15:48 -05:00
parent 18dc0e6a9f
commit ca51512ef9

View File

@ -2,85 +2,123 @@ package sam3
import ( import (
"bufio" "bufio"
"bytes" "context"
"errors"
"fmt" "fmt"
"strings" "strings"
"time"
"github.com/go-i2p/i2pkeys" "github.com/go-i2p/i2pkeys"
) )
// SAMResolver handles name resolution for I2P addresses
type SAMResolver struct { type SAMResolver struct {
*SAM sam *SAM
} }
// ResolveResult represents the possible outcomes of name resolution
type ResolveResult struct {
Address i2pkeys.I2PAddr
Error error
}
const (
defaultTimeout = 30 * time.Second
samReplyPrefix = "NAMING REPLY "
)
// NewSAMResolver creates a resolver from an existing SAM instance
func NewSAMResolver(parent *SAM) (*SAMResolver, error) { func NewSAMResolver(parent *SAM) (*SAMResolver, error) {
log.Debug("Creating new SAMResolver from existing SAM instance") if parent == nil {
var s SAMResolver return nil, fmt.Errorf("parent SAM instance required")
s.SAM = parent }
return &s, nil return &SAMResolver{sam: parent}, nil
} }
// NewFullSAMResolver creates a new resolver with its own SAM connection
func NewFullSAMResolver(address string) (*SAMResolver, error) { func NewFullSAMResolver(address string) (*SAMResolver, error) {
log.WithField("address", address).Debug("Creating new full SAMResolver") sam, err := NewSAM(address)
var s SAMResolver
var err error
s.SAM, err = NewSAM(address)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create new SAM instance") return nil, fmt.Errorf("creating SAM connection: %w", err)
return nil, err
} }
return &s, nil return &SAMResolver{sam: sam}, nil
} }
// Performs a lookup, probably this order: 1) routers known addresses, cached func (r *SAMResolver) Resolve(name string) (i2pkeys.I2PAddr, error) {
// addresses, 3) by asking peers in the I2P network. return r.ResolveWithContext(context.Background(), name)
func (sam *SAMResolver) Resolve(name string) (i2pkeys.I2PAddr, error) { }
log.WithField("name", name).Debug("Resolving name")
query := []byte(fmt.Sprintf("NAMING LOOKUP NAME=%s\n", name)) // Resolve looks up an I2P address by name with context support
if _, err := sam.conn.Write(query); err != nil { func (r *SAMResolver) ResolveWithContext(ctx context.Context, name string) (i2pkeys.I2PAddr, error) {
log.WithError(err).Error("Failed to write to SAM connection") if name == "" {
sam.Close() return "", fmt.Errorf("name cannot be empty")
return i2pkeys.I2PAddr(""), err }
}
buf := make([]byte, 4096) // Create query
n, err := sam.conn.Read(buf) query := fmt.Sprintf("NAMING LOOKUP NAME=%s\n", name)
if err != nil {
log.WithError(err).Error("Failed to read from SAM connection") // Set up timeout if context doesn't have one
sam.Close() if _, hasTimeout := ctx.Deadline(); !hasTimeout {
return i2pkeys.I2PAddr(""), err var cancel context.CancelFunc
} ctx, cancel = context.WithTimeout(ctx, defaultTimeout)
if n <= 13 || !strings.HasPrefix(string(buf[:n]), "NAMING REPLY ") { defer cancel()
log.Error("Failed to parse SAM response") }
return i2pkeys.I2PAddr(""), errors.New("Failed to parse.")
} // Write query with context awareness
s := bufio.NewScanner(bytes.NewReader(buf[13:n])) if err := r.writeWithContext(ctx, query); err != nil {
s.Split(bufio.ScanWords) return "", fmt.Errorf("writing query: %w", err)
}
for s.Scan() {
text := s.Text() // Read and parse response
log.WithField("text", text).Debug("Parsing SAM response token") return r.readResponse(ctx, name)
// log.Println("SAM3", text) }
if text == "RESULT=OK" {
continue func (r *SAMResolver) writeWithContext(ctx context.Context, query string) error {
} else if text == "RESULT=INVALID_KEY" { done := make(chan error, 1)
log.Error("Invalid key in resolver")
return i2pkeys.I2PAddr(""), fmt.Errorf("Invalid key - resolver") go func() {
} else if text == "RESULT=KEY_NOT_FOUND" { _, err := r.sam.conn.Write([]byte(query))
log.WithField("name", name).Error("Unable to resolve name") done <- err
return i2pkeys.I2PAddr(""), fmt.Errorf("Unable to resolve %s", name) }()
} else if text == "NAME="+name {
continue select {
} else if strings.HasPrefix(text, "VALUE=") { case err := <-done:
addr := i2pkeys.I2PAddr(text[6:]) return err
log.WithField("addr", addr).Debug("Name resolved successfully") case <-ctx.Done():
return i2pkeys.I2PAddr(text[6:]), nil return ctx.Err()
} else if strings.HasPrefix(text, "MESSAGE=") { }
log.WithField("message", text[8:]).Warn("Received message from SAM") }
return i2pkeys.I2PAddr(""), fmt.Errorf("Received message from SAM: %s", text[8:])
} else { func (r *SAMResolver) readResponse(ctx context.Context, name string) (i2pkeys.I2PAddr, error) {
continue reader := bufio.NewReader(r.sam.conn)
}
} // Read first line
return i2pkeys.I2PAddr(""), fmt.Errorf("Unable to resolve %s", name) line, err := reader.ReadString('\n')
if err != nil {
return "", fmt.Errorf("reading response: %w", err)
}
if !strings.HasPrefix(line, samReplyPrefix) {
return "", fmt.Errorf("invalid response format")
}
// Parse response
fields := strings.Fields(strings.TrimPrefix(line, samReplyPrefix))
for _, field := range fields {
switch {
case field == "RESULT=OK":
continue
case field == "RESULT=INVALID_KEY":
return "", fmt.Errorf("invalid key")
case field == "RESULT=KEY_NOT_FOUND":
return "", fmt.Errorf("name not found: %s", name)
case field == "NAME="+name:
continue
case strings.HasPrefix(field, "VALUE="):
return i2pkeys.I2PAddr(strings.TrimPrefix(field, "VALUE=")), nil
case strings.HasPrefix(field, "MESSAGE="):
return "", fmt.Errorf("SAM error: %s", strings.TrimPrefix(field, "MESSAGE="))
}
}
return "", fmt.Errorf("unable to resolve %s", name)
} }