From 40e34d70896ab5442fa958ba609a8468d7af734b Mon Sep 17 00:00:00 2001 From: eyedeekay Date: Fri, 29 Nov 2024 17:15:40 -0500 Subject: [PATCH] Simplify, simplify, simplify --- new.go | 213 ++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 143 insertions(+), 70 deletions(-) diff --git a/new.go b/new.go index 901da84..9f0de89 100644 --- a/new.go +++ b/new.go @@ -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 +} \ No newline at end of file