Simplify, simplify, simplify

This commit is contained in:
eyedeekay
2024-11-29 17:15:40 -05:00
parent f82fb12470
commit 40e34d7089

213
new.go
View File

@ -1,80 +1,153 @@
package i2pkeys
import (
"bufio"
"context"
"fmt"
"net"
"strings"
"github.com/sirupsen/logrus"
"time"
)
/*
HELLO VERSION MIN=3.1 MAX=3.1
DEST GENERATE SIGNATURE_TYPE=7
*/
func NewDestination() (*I2PKeys, error) {
removeNewlines := func(s string) string {
return strings.ReplaceAll(strings.ReplaceAll(s, "\r\n", ""), "\n", "")
}
//
log.Debug("Creating new destination via SAM")
conn, err := net.Dial("tcp", "127.0.0.1:7656")
if err != nil {
return nil, err
}
defer conn.Close()
_, err = conn.Write([]byte("HELLO VERSION MIN=3.1 MAX=3.1\n"))
if err != nil {
log.WithError(err).Error("Error writing to SAM bridge")
return nil, err
}
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if err != nil {
log.WithError(err).Error("Error reading from SAM bridge")
return nil, err
}
if n < 1 {
log.Error("No data received from SAM bridge")
return nil, fmt.Errorf("no data received")
}
const (
defaultSAMAddress = "127.0.0.1:7656"
defaultTimeout = 30 * time.Second
maxResponseSize = 4096
cmdHello = "HELLO VERSION MIN=3.1 MAX=3.1\n"
cmdGenerate = "DEST GENERATE SIGNATURE_TYPE=7\n"
responseOK = "RESULT=OK"
pubKeyPrefix = "PUB="
privKeyPrefix = "PRIV="
)
response := string(buf[:n])
log.WithField("response", response).Debug("Received response from SAM bridge")
if strings.Contains(string(buf[:n]), "RESULT=OK") {
_, err = conn.Write([]byte("DEST GENERATE SIGNATURE_TYPE=7\n"))
if err != nil {
log.WithError(err).Error("Error writing DEST GENERATE to SAM bridge")
return nil, err
}
n, err = conn.Read(buf)
if err != nil {
log.WithError(err).Error("Error reading destination from SAM bridge")
return nil, err
}
if n < 1 {
log.Error("No destination data received from SAM bridge")
return nil, fmt.Errorf("no destination data received")
}
pub := strings.Split(strings.Split(string(buf[:n]), "PRIV=")[0], "PUB=")[1]
_priv := strings.Split(string(buf[:n]), "PRIV=")[1]
priv := removeNewlines(_priv) //There is an extraneous newline in the private key, so we'll remove it.
log.WithFields(logrus.Fields{
"_priv(pre-newline removal)": _priv,
"priv": priv,
}).Debug("Removed newline")
log.Debug("Successfully created new destination")
return &I2PKeys{
Address: I2PAddr(pub),
Both: pub + priv,
}, nil
}
log.Error("No RESULT=OK received from SAM bridge")
return nil, fmt.Errorf("no result received")
// samClient handles communication with the SAM bridge
type samClient struct {
addr string
timeout time.Duration
}
// newSAMClient creates a new SAM client with optional configuration
func newSAMClient(options ...func(*samClient)) *samClient {
client := &samClient{
addr: defaultSAMAddress,
timeout: defaultTimeout,
}
for _, opt := range options {
opt(client)
}
return client
}
// NewDestination generates a new I2P destination using the SAM bridge.
// This is the only public function that external code should use.
func NewDestination() (*I2PKeys, error) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()
client := newSAMClient()
return client.generateDestination(ctx)
}
// generateDestination handles the key generation process
func (c *samClient) generateDestination(ctx context.Context) (*I2PKeys, error) {
conn, err := c.dial(ctx)
if err != nil {
return nil, fmt.Errorf("connecting to SAM bridge: %w", err)
}
defer conn.Close()
if err := c.handshake(ctx, conn); err != nil {
return nil, fmt.Errorf("SAM handshake failed: %w", err)
}
keys, err := c.generateKeys(ctx, conn)
if err != nil {
return nil, fmt.Errorf("generating keys: %w", err)
}
return keys, nil
}
func (c *samClient) dial(ctx context.Context) (net.Conn, error) {
dialer := &net.Dialer{Timeout: c.timeout}
conn, err := dialer.DialContext(ctx, "tcp", c.addr)
if err != nil {
return nil, fmt.Errorf("dialing SAM bridge: %w", err)
}
return conn, nil
}
func (c *samClient) handshake(ctx context.Context, conn net.Conn) error {
if err := c.writeCommand(conn, cmdHello); err != nil {
return err
}
response, err := c.readResponse(conn)
if err != nil {
return err
}
if !strings.Contains(response, responseOK) {
return fmt.Errorf("unexpected SAM response: %s", response)
}
return nil
}
func (c *samClient) generateKeys(ctx context.Context, conn net.Conn) (*I2PKeys, error) {
if err := c.writeCommand(conn, cmdGenerate); err != nil {
return nil, err
}
response, err := c.readResponse(conn)
if err != nil {
return nil, err
}
pub, priv, err := parseKeyResponse(response)
if err != nil {
return nil, err
}
return &I2PKeys{
Address: I2PAddr(pub),
Both: pub + priv,
}, nil
}
func (c *samClient) writeCommand(conn net.Conn, cmd string) error {
_, err := conn.Write([]byte(cmd))
if err != nil {
return fmt.Errorf("writing command: %w", err)
}
return nil
}
func (c *samClient) readResponse(conn net.Conn) (string, error) {
reader := bufio.NewReader(conn)
response, err := reader.ReadString('\n')
if err != nil {
return "", fmt.Errorf("reading response: %w", err)
}
return strings.TrimSpace(response), nil
}
func parseKeyResponse(response string) (pub, priv string, err error) {
parts := strings.Split(response, privKeyPrefix)
if len(parts) != 2 {
return "", "", fmt.Errorf("invalid key response format")
}
pubParts := strings.Split(parts[0], pubKeyPrefix)
if len(pubParts) != 2 {
return "", "", fmt.Errorf("invalid public key format")
}
pub = strings.TrimSpace(pubParts[1])
priv = strings.TrimSpace(parts[1])
return pub, priv, nil
}