compatibility layer

This commit is contained in:
eyedeekay
2025-05-29 19:47:50 -04:00
parent 4f85e7f1c7
commit 27794b1002
12 changed files with 1265 additions and 0 deletions

204
config.go Normal file
View File

@ -0,0 +1,204 @@
package sam3
import (
"strconv"
"github.com/go-i2p/go-sam-go/common"
)
// I2PConfig manages I2P configuration options
type I2PConfig struct {
*common.I2PConfig
}
// NewConfig creates a new I2PConfig
func NewConfig(opts ...func(*I2PConfig) error) (*I2PConfig, error) {
baseConfig, err := common.NewConfig()
if err != nil {
return nil, err
}
config := &I2PConfig{
I2PConfig: baseConfig,
}
for _, opt := range opts {
if err := opt(config); err != nil {
return nil, err
}
}
return config, nil
}
// All the configuration method forwards
func (f *I2PConfig) SetSAMAddress(addr string) {
f.I2PConfig.SetSAMAddress(addr)
}
func (f *I2PConfig) Sam() string {
return f.I2PConfig.Sam()
}
func (f *I2PConfig) SAMAddress() string {
return f.I2PConfig.SAMAddress()
}
func (f *I2PConfig) ID() string {
return f.I2PConfig.ID()
}
func (f *I2PConfig) Print() []string {
return f.I2PConfig.Print()
}
func (f *I2PConfig) SessionStyle() string {
return f.I2PConfig.SessionStyle()
}
func (f *I2PConfig) MinSAM() string {
return f.I2PConfig.MinSAM()
}
func (f *I2PConfig) MaxSAM() string {
return f.I2PConfig.MaxSAM()
}
func (f *I2PConfig) DestinationKey() string {
return f.I2PConfig.DestinationKey()
}
func (f *I2PConfig) SignatureType() string {
return f.I2PConfig.SignatureType()
}
func (f *I2PConfig) ToPort() string {
return f.I2PConfig.ToPort()
}
func (f *I2PConfig) Reduce() string {
return f.I2PConfig.Reduce()
}
func (f *I2PConfig) Reliability() string {
return f.I2PConfig.Reliability()
}
// Configuration option setters for all the missing Set* functions
func SetInAllowZeroHop(s string) func(*I2PConfig) error {
return func(e *I2PConfig) error {
e.InAllowZeroHop = s == "true"
return nil
}
}
func SetOutAllowZeroHop(s string) func(*I2PConfig) error {
return func(e *I2PConfig) error {
e.OutAllowZeroHop = s == "true"
return nil
}
}
func SetInLength(s string) func(*I2PConfig) error {
return func(e *I2PConfig) error {
if i, err := strconv.Atoi(s); err == nil {
e.InLength = i
}
return nil
}
}
func SetOutLength(s string) func(*I2PConfig) error {
return func(e *I2PConfig) error {
if i, err := strconv.Atoi(s); err == nil {
e.OutLength = i
}
return nil
}
}
func SetInQuantity(s string) func(*I2PConfig) error {
return func(e *I2PConfig) error {
if i, err := strconv.Atoi(s); err == nil {
e.InQuantity = i
}
return nil
}
}
func SetOutQuantity(s string) func(*I2PConfig) error {
return func(e *I2PConfig) error {
if i, err := strconv.Atoi(s); err == nil {
e.OutQuantity = i
}
return nil
}
}
func SetInVariance(s string) func(*I2PConfig) error {
return func(e *I2PConfig) error {
if i, err := strconv.Atoi(s); err == nil {
e.InVariance = i
}
return nil
}
}
func SetOutVariance(s string) func(*I2PConfig) error {
return func(e *I2PConfig) error {
if i, err := strconv.Atoi(s); err == nil {
e.OutVariance = i
}
return nil
}
}
func SetInBackupQuantity(s string) func(*I2PConfig) error {
return func(e *I2PConfig) error {
if i, err := strconv.Atoi(s); err == nil {
e.InBackupQuantity = i
}
return nil
}
}
func SetOutBackupQuantity(s string) func(*I2PConfig) error {
return func(e *I2PConfig) error {
if i, err := strconv.Atoi(s); err == nil {
e.OutBackupQuantity = i
}
return nil
}
}
func SetUseCompression(s string) func(*I2PConfig) error {
return func(e *I2PConfig) error {
e.UseCompression = s == "true"
return nil
}
}
func SetReduceIdleTime(s string) func(*I2PConfig) error {
return func(e *I2PConfig) error {
if i, err := strconv.Atoi(s); err == nil {
e.ReduceIdleTime = i
}
return nil
}
}
func SetCloseIdleTime(s string) func(*I2PConfig) error {
return func(e *I2PConfig) error {
if i, err := strconv.Atoi(s); err == nil {
e.CloseIdleTime = i
}
return nil
}
}
func SetAccessListType(s string) func(*I2PConfig) error {
return func(e *I2PConfig) error {
e.AccessListType = s
return nil
}
}

51
conn.go Normal file
View File

@ -0,0 +1,51 @@
package sam3
import (
"net"
"time"
)
// SAMConn implements net.Conn for I2P connections
type SAMConn struct {
conn net.Conn
}
// Read reads data from the connection
func (sc *SAMConn) Read(buf []byte) (int, error) {
return sc.conn.Read(buf)
}
// Write writes data to the connection
func (sc *SAMConn) Write(buf []byte) (int, error) {
return sc.conn.Write(buf)
}
// Close closes the connection
func (sc *SAMConn) Close() error {
return sc.conn.Close()
}
// LocalAddr returns the local address
func (sc *SAMConn) LocalAddr() net.Addr {
return sc.conn.LocalAddr()
}
// RemoteAddr returns the remote address
func (sc *SAMConn) RemoteAddr() net.Addr {
return sc.conn.RemoteAddr()
}
// SetDeadline sets read and write deadlines
func (sc *SAMConn) SetDeadline(t time.Time) error {
return sc.conn.SetDeadline(t)
}
// SetReadDeadline sets read deadline
func (sc *SAMConn) SetReadDeadline(t time.Time) error {
return sc.conn.SetReadDeadline(t)
}
// SetWriteDeadline sets write deadline
func (sc *SAMConn) SetWriteDeadline(t time.Time) error {
return sc.conn.SetWriteDeadline(t)
}

129
datagram.go Normal file
View File

@ -0,0 +1,129 @@
package sam3
import (
"net"
"time"
"github.com/go-i2p/go-sam-go/common"
"github.com/go-i2p/go-sam-go/datagram"
"github.com/go-i2p/i2pkeys"
)
// DatagramSession implements net.PacketConn for I2P datagrams
type DatagramSession struct {
session *datagram.DatagramSession
sam *common.SAM
}
// NewDatagramSession creates a new datagram session
func (s *SAM) NewDatagramSession(id string, keys i2pkeys.I2PKeys, options []string, udpPort int) (*DatagramSession, error) {
session, err := datagram.NewDatagramSession(s.sam, id, keys, options)
if err != nil {
return nil, err
}
return &DatagramSession{
session: session,
sam: s.sam,
}, nil
}
// ReadFrom reads a datagram from the session
func (s *DatagramSession) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
return s.session.ReadFrom(b)
}
// WriteTo writes a datagram to the specified address
func (s *DatagramSession) WriteTo(b []byte, addr net.Addr) (n int, err error) {
return s.session.WriteTo(b, addr)
}
// Close closes the datagram session
func (s *DatagramSession) Close() error {
return s.session.Close()
}
// LocalAddr returns the local address
func (s *DatagramSession) LocalAddr() net.Addr {
return s.session.LocalAddr()
}
// LocalI2PAddr returns the I2P destination
func (s *DatagramSession) LocalI2PAddr() i2pkeys.I2PAddr {
return s.session.Addr()
}
// SetDeadline sets read and write deadlines
func (s *DatagramSession) SetDeadline(t time.Time) error {
return s.session.SetDeadline(t)
}
// SetReadDeadline sets read deadline
func (s *DatagramSession) SetReadDeadline(t time.Time) error {
return s.session.SetReadDeadline(t)
}
// SetWriteDeadline sets write deadline
func (s *DatagramSession) SetWriteDeadline(t time.Time) error {
return s.session.SetWriteDeadline(t)
}
// Read reads from the session
func (s *DatagramSession) Read(b []byte) (n int, err error) {
return s.session.Read(b)
}
// Write writes to the session
func (s *DatagramSession) Write(b []byte) (int, error) {
return s.session.Write(b)
}
// Addr returns the session address
func (s *DatagramSession) Addr() net.Addr {
return s.session.LocalAddr()
}
// B32 returns the base32 address
func (s *DatagramSession) B32() string {
return s.session.Addr().Base32()
}
// RemoteAddr returns the remote address
func (s *DatagramSession) RemoteAddr() net.Addr {
return s.session.RemoteAddr()
}
// Lookup performs name lookup
func (s *DatagramSession) Lookup(name string) (a net.Addr, err error) {
addr, err := s.sam.Lookup(name)
if err != nil {
return nil, err
}
return addr, nil
}
// Accept accepts connections (not applicable for datagrams)
func (s *DatagramSession) Accept() (net.Conn, error) {
return nil, net.ErrClosed
}
// Dial dials a connection (returns self for datagrams)
func (s *DatagramSession) Dial(net string, addr string) (*DatagramSession, error) {
return s, nil
}
// DialI2PRemote dials to I2P remote
func (s *DatagramSession) DialI2PRemote(net string, addr net.Addr) (*DatagramSession, error) {
return s, nil
}
// DialRemote dials to remote address
func (s *DatagramSession) DialRemote(net, addr string) (net.PacketConn, error) {
return s, nil
}
// SetWriteBuffer sets write buffer size
func (s *DatagramSession) SetWriteBuffer(bytes int) error {
// Not implemented in underlying library
return nil
}

97
emit.go Normal file
View File

@ -0,0 +1,97 @@
package sam3
import (
"github.com/go-i2p/go-sam-go/common"
)
// SAMEmit handles SAM protocol message generation
type SAMEmit struct {
I2PConfig
emit *common.SAMEmit
}
// NewEmit creates a new SAMEmit
func NewEmit(opts ...func(*SAMEmit) error) (*SAMEmit, error) {
config, err := NewConfig()
if err != nil {
return nil, err
}
emit := &SAMEmit{
I2PConfig: *config,
emit: &common.SAMEmit{I2PConfig: *config.I2PConfig},
}
for _, opt := range opts {
if err := opt(emit); err != nil {
return nil, err
}
}
return emit, nil
}
// Hello generates hello message
func (e *SAMEmit) Hello() string {
return e.emit.Hello()
}
// HelloBytes generates hello message as bytes
func (e *SAMEmit) HelloBytes() []byte {
return e.emit.HelloBytes()
}
// Create generates session create message
func (e *SAMEmit) Create() string {
return e.emit.Create()
}
// CreateBytes generates session create message as bytes
func (e *SAMEmit) CreateBytes() []byte {
return e.emit.CreateBytes()
}
// Connect generates connect message
func (e *SAMEmit) Connect(dest string) string {
return e.emit.Connect(dest)
}
// ConnectBytes generates connect message as bytes
func (e *SAMEmit) ConnectBytes(dest string) []byte {
return e.emit.ConnectBytes(dest)
}
// Accept generates accept message
func (e *SAMEmit) Accept() string {
return e.emit.Accept()
}
// AcceptBytes generates accept message as bytes
func (e *SAMEmit) AcceptBytes() []byte {
return e.emit.AcceptBytes()
}
// Lookup generates lookup message
func (e *SAMEmit) Lookup(name string) string {
return e.emit.Lookup(name)
}
// LookupBytes generates lookup message as bytes
func (e *SAMEmit) LookupBytes(name string) []byte {
return e.emit.LookupBytes(name)
}
// GenerateDestination generates destination message
func (e *SAMEmit) GenerateDestination() string {
return e.emit.GenerateDestination()
}
// GenerateDestinationBytes generates destination message as bytes
func (e *SAMEmit) GenerateDestinationBytes() []byte {
return e.emit.GenerateDestinationBytes()
}
// SamOptionsString returns SAM options as string
func (e *SAMEmit) SamOptionsString() string {
return e.emit.SamOptionsString()
}

50
listener.go Normal file
View File

@ -0,0 +1,50 @@
package sam3
import (
"net"
"github.com/go-i2p/go-sam-go/stream"
)
// StreamListener implements net.Listener for I2P streams
type StreamListener struct {
listener *stream.StreamListener
}
// Accept accepts new inbound connections
func (l *StreamListener) Accept() (net.Conn, error) {
conn, err := l.listener.Accept()
if err != nil {
return nil, err
}
return &SAMConn{conn: conn}, nil
}
// AcceptI2P accepts a new inbound I2P connection
func (l *StreamListener) AcceptI2P() (*SAMConn, error) {
conn, err := l.listener.Accept()
if err != nil {
return nil, err
}
return &SAMConn{conn: conn}, nil
}
// Addr returns the listener's address
func (l *StreamListener) Addr() net.Addr {
return l.listener.Addr()
}
// Close closes the listener
func (l *StreamListener) Close() error {
return l.listener.Close()
}
// From returns the from port
func (l *StreamListener) From() string {
return ""
}
// To returns the to port
func (l *StreamListener) To() string {
return ""
}

165
primary.go Normal file
View File

@ -0,0 +1,165 @@
package sam3
import (
"net"
"time"
"github.com/go-i2p/go-sam-go/common"
"github.com/go-i2p/i2pkeys"
)
// PrimarySession represents a primary session
type PrimarySession struct {
Timeout time.Duration
Deadline time.Time
Config SAMEmit
sam *common.SAM
keys i2pkeys.I2PKeys
id string
}
// NewPrimarySession creates a new PrimarySession
func (sam *SAM) NewPrimarySession(id string, keys i2pkeys.I2PKeys, options []string) (*PrimarySession, error) {
return &PrimarySession{
Config: sam.Config,
sam: sam.sam,
keys: keys,
id: id,
}, nil
}
// NewPrimarySessionWithSignature creates a new PrimarySession with signature
func (sam *SAM) NewPrimarySessionWithSignature(id string, keys i2pkeys.I2PKeys, options []string, sigType string) (*PrimarySession, error) {
return &PrimarySession{
Config: sam.Config,
sam: sam.sam,
keys: keys,
id: id,
}, nil
}
// ID returns the session ID
func (ss *PrimarySession) ID() string {
return ss.id
}
// Keys returns the session keys
func (ss *PrimarySession) Keys() i2pkeys.I2PKeys {
return ss.keys
}
// Addr returns the I2P address
func (ss *PrimarySession) Addr() i2pkeys.I2PAddr {
return ss.keys.Addr()
}
// Close closes the session
func (ss *PrimarySession) Close() error {
return nil
}
// NewStreamSubSession creates a new stream sub-session
func (sam *PrimarySession) NewStreamSubSession(id string) (*StreamSession, error) {
samWrapper := &SAM{sam: sam.sam}
return samWrapper.NewStreamSession(id, sam.keys, nil)
}
// NewStreamSubSessionWithPorts creates a new stream sub-session with ports
func (sam *PrimarySession) NewStreamSubSessionWithPorts(id, from, to string) (*StreamSession, error) {
samWrapper := &SAM{sam: sam.sam}
return samWrapper.NewStreamSessionWithSignatureAndPorts(id, from, to, sam.keys, nil, Sig_EdDSA_SHA512_Ed25519)
}
// NewUniqueStreamSubSession creates a unique stream sub-session
func (sam *PrimarySession) NewUniqueStreamSubSession(id string) (*StreamSession, error) {
return sam.NewStreamSubSession(id + RandString())
}
// NewDatagramSubSession creates a new datagram sub-session
func (s *PrimarySession) NewDatagramSubSession(id string, udpPort int) (*DatagramSession, error) {
samWrapper := &SAM{sam: s.sam}
return samWrapper.NewDatagramSession(id, s.keys, nil, udpPort)
}
// NewRawSubSession creates a new raw sub-session
func (s *PrimarySession) NewRawSubSession(id string, udpPort int) (*RawSession, error) {
samWrapper := &SAM{sam: s.sam}
return samWrapper.NewRawSession(id, s.keys, nil, udpPort)
}
// Dial implements net.Dialer
func (sam *PrimarySession) Dial(network, addr string) (net.Conn, error) {
ss, err := sam.NewStreamSubSession("dial-" + RandString())
if err != nil {
return nil, err
}
return ss.Dial(network, addr)
}
// DialTCP implements x/dialer
func (sam *PrimarySession) DialTCP(network string, laddr, raddr net.Addr) (net.Conn, error) {
return sam.Dial(network, raddr.String())
}
// DialTCPI2P dials TCP over I2P
func (sam *PrimarySession) DialTCPI2P(network string, laddr, raddr string) (net.Conn, error) {
return sam.Dial(network, raddr)
}
// DialUDP implements x/dialer
func (sam *PrimarySession) DialUDP(network string, laddr, raddr net.Addr) (net.PacketConn, error) {
ds, err := sam.NewDatagramSubSession("udp-"+RandString(), 0)
if err != nil {
return nil, err
}
return ds, nil
}
// DialUDPI2P dials UDP over I2P
func (sam *PrimarySession) DialUDPI2P(network, laddr, raddr string) (*DatagramSession, error) {
return sam.NewDatagramSubSession("udp-"+RandString(), 0)
}
// Lookup performs name lookup
func (s *PrimarySession) Lookup(name string) (a net.Addr, err error) {
addr, err := s.sam.Lookup(name)
if err != nil {
return nil, err
}
return addr, nil
}
// Resolve resolves network address
func (sam *PrimarySession) Resolve(network, addr string) (net.Addr, error) {
return sam.Lookup(addr)
}
// ResolveTCPAddr resolves TCP address
func (sam *PrimarySession) ResolveTCPAddr(network, dest string) (net.Addr, error) {
return sam.Lookup(dest)
}
// ResolveUDPAddr resolves UDP address
func (sam *PrimarySession) ResolveUDPAddr(network, dest string) (net.Addr, error) {
return sam.Lookup(dest)
}
// LocalAddr returns local address
func (ss *PrimarySession) LocalAddr() net.Addr {
return ss.keys.Addr()
}
// From returns from port
func (ss *PrimarySession) From() string {
return ""
}
// To returns to port
func (ss *PrimarySession) To() string {
return ""
}
// SignatureType returns signature type
func (ss *PrimarySession) SignatureType() string {
return ""
}

63
raw.go Normal file
View File

@ -0,0 +1,63 @@
package sam3
import (
"time"
"github.com/go-i2p/go-sam-go/common"
"github.com/go-i2p/go-sam-go/raw"
"github.com/go-i2p/i2pkeys"
)
// RawSession provides raw datagram messaging
type RawSession struct {
session *raw.RawSession
sam *common.SAM
}
// NewRawSession creates a new raw session
func (s *SAM) NewRawSession(id string, keys i2pkeys.I2PKeys, options []string, udpPort int) (*RawSession, error) {
session, err := raw.NewRawSession(s.sam, id, keys, options)
if err != nil {
return nil, err
}
return &RawSession{
session: session,
sam: s.sam,
}, nil
}
// Read reads one raw datagram
func (s *RawSession) Read(b []byte) (n int, err error) {
return s.session.Read(b)
}
// WriteTo sends one raw datagram to the destination
func (s *RawSession) WriteTo(b []byte, addr i2pkeys.I2PAddr) (n int, err error) {
return s.session.WriteTo(b, addr)
}
// Close closes the raw session
func (s *RawSession) Close() error {
return s.session.Close()
}
// LocalAddr returns the local I2P destination
func (s *RawSession) LocalAddr() i2pkeys.I2PAddr {
return s.session.Addr()
}
// SetDeadline sets read and write deadlines
func (s *RawSession) SetDeadline(t time.Time) error {
return s.session.SetDeadline(t)
}
// SetReadDeadline sets read deadline
func (s *RawSession) SetReadDeadline(t time.Time) error {
return s.session.SetReadDeadline(t)
}
// SetWriteDeadline sets write deadline
func (s *RawSession) SetWriteDeadline(t time.Time) error {
return s.session.SetWriteDeadline(t)
}

44
resolver.go Normal file
View File

@ -0,0 +1,44 @@
package sam3
import (
"github.com/go-i2p/go-sam-go/common"
"github.com/go-i2p/i2pkeys"
)
// SAMResolver provides name resolution functionality
type SAMResolver struct {
*SAM
resolver *common.SAMResolver
}
// NewSAMResolver creates a new SAMResolver from existing SAM
func NewSAMResolver(parent *SAM) (*SAMResolver, error) {
resolver, err := common.NewSAMResolver(parent.sam)
if err != nil {
return nil, err
}
return &SAMResolver{
SAM: parent,
resolver: resolver,
}, nil
}
// NewFullSAMResolver creates a new full SAMResolver
func NewFullSAMResolver(address string) (*SAMResolver, error) {
resolver, err := common.NewFullSAMResolver(address)
if err != nil {
return nil, err
}
sam := &SAM{sam: resolver.SAM}
return &SAMResolver{
SAM: sam,
resolver: resolver,
}, nil
}
// Resolve performs a lookup
func (sam *SAMResolver) Resolve(name string) (i2pkeys.I2PAddr, error) {
return sam.resolver.Resolve(name)
}

134
sam3.go Normal file
View File

@ -0,0 +1,134 @@
// Package sam3 provides a compatibility layer for the go-i2p/sam3 library using go-sam-go as the backend
package sam3
import (
"io"
"github.com/go-i2p/go-sam-go/common"
"github.com/go-i2p/i2pkeys"
)
// Constants from original sam3
const (
Sig_NONE = "SIGNATURE_TYPE=EdDSA_SHA512_Ed25519"
Sig_DSA_SHA1 = "SIGNATURE_TYPE=DSA_SHA1"
Sig_ECDSA_SHA256_P256 = "SIGNATURE_TYPE=ECDSA_SHA256_P256"
Sig_ECDSA_SHA384_P384 = "SIGNATURE_TYPE=ECDSA_SHA384_P384"
Sig_ECDSA_SHA512_P521 = "SIGNATURE_TYPE=ECDSA_SHA512_P521"
Sig_EdDSA_SHA512_Ed25519 = "SIGNATURE_TYPE=EdDSA_SHA512_Ed25519"
)
// Predefined option sets (keeping your existing definitions)
var (
Options_Humongous = []string{
"inbound.length=3", "outbound.length=3",
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
"inbound.backupQuantity=3", "outbound.backupQuantity=3",
"inbound.quantity=6", "outbound.quantity=6",
}
Options_Large = []string{
"inbound.length=3", "outbound.length=3",
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
"inbound.backupQuantity=1", "outbound.backupQuantity=1",
"inbound.quantity=4", "outbound.quantity=4",
}
Options_Wide = []string{
"inbound.length=1", "outbound.length=1",
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
"inbound.backupQuantity=2", "outbound.backupQuantity=2",
"inbound.quantity=3", "outbound.quantity=3",
}
Options_Medium = []string{
"inbound.length=3", "outbound.length=3",
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
"inbound.backupQuantity=0", "outbound.backupQuantity=0",
"inbound.quantity=2", "outbound.quantity=2",
}
Options_Default = []string{
"inbound.length=3", "outbound.length=3",
"inbound.lengthVariance=0", "outbound.lengthVariance=0",
"inbound.backupQuantity=1", "outbound.backupQuantity=1",
"inbound.quantity=1", "outbound.quantity=1",
}
Options_Small = []string{
"inbound.length=3", "outbound.length=3",
"inbound.lengthVariance=1", "outbound.lengthVariance=1",
"inbound.backupQuantity=0", "outbound.backupQuantity=0",
"inbound.quantity=1", "outbound.quantity=1",
}
Options_Warning_ZeroHop = []string{
"inbound.length=0", "outbound.length=0",
"inbound.lengthVariance=0", "outbound.lengthVariance=0",
"inbound.backupQuantity=0", "outbound.backupQuantity=0",
"inbound.quantity=2", "outbound.quantity=2",
}
)
// Global variables from original sam3
var (
PrimarySessionSwitch string = PrimarySessionString()
SAM_HOST = getEnv("sam_host", "127.0.0.1")
SAM_PORT = getEnv("sam_port", "7656")
)
// SAM represents the main controller for I2P router's SAM bridge
type SAM struct {
Config SAMEmit
sam *common.SAM
}
// NewSAM creates a new controller for the I2P routers SAM bridge
func NewSAM(address string) (*SAM, error) {
samInstance, err := common.NewSAM(address)
if err != nil {
return nil, err
}
config, err := NewConfig()
if err != nil {
return nil, err
}
emit := SAMEmit{I2PConfig: *config}
return &SAM{
Config: emit,
sam: samInstance,
}, nil
}
// Close closes this sam session
func (sam *SAM) Close() error {
return sam.sam.Close()
}
// Keys returns the keys associated with this SAM instance
func (sam *SAM) Keys() (k *i2pkeys.I2PKeys) {
return sam.sam.Keys()
}
// NewKeys creates the I2P-equivalent of an IP address
func (sam *SAM) NewKeys(sigType ...string) (i2pkeys.I2PKeys, error) {
return sam.sam.NewKeys(sigType...)
}
// ReadKeys reads public/private keys from an io.Reader
func (sam *SAM) ReadKeys(r io.Reader) (err error) {
return sam.sam.ReadKeys(r)
}
// EnsureKeyfile ensures keyfile exists
func (sam *SAM) EnsureKeyfile(fname string) (keys i2pkeys.I2PKeys, err error) {
return sam.sam.EnsureKeyfile(fname)
}
// Lookup performs a name lookup
func (sam *SAM) Lookup(name string) (i2pkeys.I2PAddr, error) {
return sam.sam.Lookup(name)
}

191
stream.go Normal file
View File

@ -0,0 +1,191 @@
package sam3
import (
"context"
"net"
"time"
"github.com/go-i2p/go-sam-go/common"
"github.com/go-i2p/go-sam-go/stream"
"github.com/go-i2p/i2pkeys"
)
// StreamSession represents a streaming session
type StreamSession struct {
Timeout time.Duration
Deadline time.Time
session *stream.StreamSession
sam *common.SAM
fromPort string
toPort string
signatureType string
}
// NewStreamSession creates a new StreamSession
func (sam *SAM) NewStreamSession(id string, keys i2pkeys.I2PKeys, options []string) (*StreamSession, error) {
session, err := stream.NewStreamSession(sam.sam, id, keys, options)
if err != nil {
return nil, err
}
return &StreamSession{
session: session,
sam: sam.sam,
fromPort: "",
toPort: "",
signatureType: common.SIG_EdDSA_SHA512_Ed25519,
}, nil
}
// NewStreamSessionWithSignature creates a new StreamSession with custom signature
func (sam *SAM) NewStreamSessionWithSignature(id string, keys i2pkeys.I2PKeys, options []string, sigType string) (*StreamSession, error) {
streamSAM := &stream.SAM{SAM: sam.sam}
session, err := streamSAM.NewStreamSessionWithSignature(id, keys, options, sigType)
if err != nil {
return nil, err
}
return &StreamSession{
session: session,
sam: sam.sam,
fromPort: "",
toPort: "",
signatureType: sigType,
}, nil
}
// NewStreamSessionWithSignatureAndPorts creates a new StreamSession with signature and ports
func (sam *SAM) NewStreamSessionWithSignatureAndPorts(id, from, to string, keys i2pkeys.I2PKeys, options []string, sigType string) (*StreamSession, error) {
streamSAM := &stream.SAM{SAM: sam.sam}
session, err := streamSAM.NewStreamSessionWithPorts(id, from, to, keys, options)
if err != nil {
return nil, err
}
return &StreamSession{
session: session,
sam: sam.sam,
fromPort: from,
toPort: to,
signatureType: sigType,
}, nil
}
// ID returns the local tunnel name
func (s *StreamSession) ID() string {
return s.session.ID()
}
// Keys returns the keys associated with the session
func (s *StreamSession) Keys() i2pkeys.I2PKeys {
return s.session.Keys()
}
// Addr returns the I2P destination address
func (s *StreamSession) Addr() i2pkeys.I2PAddr {
return s.session.Keys().Addr()
}
// Close closes the session
func (s *StreamSession) Close() error {
return s.session.Close()
}
// Listen creates a new stream listener
func (s *StreamSession) Listen() (*StreamListener, error) {
listener, err := s.session.Listen()
if err != nil {
return nil, err
}
return &StreamListener{
listener: listener,
}, nil
}
// Dial establishes a connection to an address
func (s *StreamSession) Dial(n, addr string) (c net.Conn, err error) {
dialer := s.session.NewDialer()
return dialer.Dial(n, addr)
}
// DialContext establishes a connection with context
func (s *StreamSession) DialContext(ctx context.Context, n, addr string) (net.Conn, error) {
dialer := s.session.NewDialer()
return dialer.DialContext(ctx, n, addr)
}
// DialContextI2P establishes an I2P connection with context
func (s *StreamSession) DialContextI2P(ctx context.Context, n, addr string) (*SAMConn, error) {
dialer := s.session.NewDialer()
conn, err := dialer.DialContext(ctx, n, addr)
if err != nil {
return nil, err
}
return &SAMConn{conn: conn}, nil
}
// DialI2P dials to an I2P destination
func (s *StreamSession) DialI2P(addr i2pkeys.I2PAddr) (*SAMConn, error) {
dialer := s.session.NewDialer()
conn, err := dialer.DialI2P(addr)
if err != nil {
return nil, err
}
return &SAMConn{conn: conn}, nil
}
// Lookup performs name lookup
func (s *StreamSession) Lookup(name string) (i2pkeys.I2PAddr, error) {
return s.sam.Lookup(name)
}
// Read reads data from the stream
func (s *StreamSession) Read(buf []byte) (int, error) {
return s.session.Read(buf)
}
// Write sends data over the stream
func (s *StreamSession) Write(data []byte) (int, error) {
return s.session.Write(data)
}
// LocalAddr returns the local address
func (s *StreamSession) LocalAddr() net.Addr {
return s.session.LocalAddr()
}
// RemoteAddr returns the remote address
func (s *StreamSession) RemoteAddr() net.Addr {
return s.session.RemoteAddr()
}
// SetDeadline sets read and write deadlines
func (s *StreamSession) SetDeadline(t time.Time) error {
return s.session.SetDeadline(t)
}
// SetReadDeadline sets read deadline
func (s *StreamSession) SetReadDeadline(t time.Time) error {
return s.session.SetReadDeadline(t)
}
// SetWriteDeadline sets write deadline
func (s *StreamSession) SetWriteDeadline(t time.Time) error {
return s.session.SetWriteDeadline(t)
}
// From returns the from port
func (s *StreamSession) From() string {
return s.fromPort
}
// To returns the to port
func (s *StreamSession) To() string {
return s.toPort
}
// SignatureType returns the signature type
func (s *StreamSession) SignatureType() string {
return s.signatureType
}

15
types.go Normal file
View File

@ -0,0 +1,15 @@
package sam3
// Options represents a map of configuration options
type Options map[string]string
// AsList returns options as a list of strings
func (opts Options) AsList() (ls []string) {
for k, v := range opts {
ls = append(ls, k+"="+v)
}
return
}
// Option is a functional option for SAMEmit
type Option func(*SAMEmit) error

122
utils.go Normal file
View File

@ -0,0 +1,122 @@
package sam3
import (
"crypto/rand"
"math/big"
"os"
"strconv"
"strings"
"github.com/sirupsen/logrus"
)
// getEnv gets environment variable with fallback
func getEnv(key, fallback string) string {
if value := os.Getenv(key); value != "" {
return value
}
return fallback
}
// RandString generates a random string
func RandString() string {
const chars = "abcdefghijklmnopqrstuvwxyz"
result := make([]byte, 12)
for i := range result {
n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(chars))))
result[i] = chars[n.Int64()]
}
return string(result)
}
// PrimarySessionString returns primary session string
func PrimarySessionString() string {
return "primary"
}
// SAMDefaultAddr returns default SAM address
func SAMDefaultAddr(fallforward string) string {
if fallforward != "" {
return fallforward
}
return SAM_HOST + ":" + SAM_PORT
}
// ExtractDest extracts destination from input
func ExtractDest(input string) string {
parts := strings.Fields(input)
for _, part := range parts {
if strings.HasPrefix(part, "DEST=") {
return part[5:]
}
}
return ""
}
// ExtractPairString extracts string value from key=value pair
func ExtractPairString(input, value string) string {
prefix := value + "="
parts := strings.Fields(input)
for _, part := range parts {
if strings.HasPrefix(part, prefix) {
return part[len(prefix):]
}
}
return ""
}
// ExtractPairInt extracts integer value from key=value pair
func ExtractPairInt(input, value string) int {
str := ExtractPairString(input, value)
if str == "" {
return 0
}
i, _ := strconv.Atoi(str)
return i
}
// GenerateOptionString generates option string from slice
func GenerateOptionString(opts []string) string {
return strings.Join(opts, " ")
}
// IgnorePortError ignores port-related errors
func IgnorePortError(err error) error {
if err != nil && strings.Contains(err.Error(), "port") {
return nil
}
return err
}
// Logging functions
var sam3Logger *logrus.Logger
// InitializeSAM3Logger initializes the logger
func InitializeSAM3Logger() {
sam3Logger = logrus.New()
sam3Logger.SetLevel(logrus.InfoLevel)
}
// GetSAM3Logger returns the initialized logger
func GetSAM3Logger() *logrus.Logger {
if sam3Logger == nil {
InitializeSAM3Logger()
}
return sam3Logger
}
// Additional utility functions that may be needed for compatibility
func ConvertOptionsToSlice(opts Options) []string {
return opts.AsList()
}
func ConvertSliceToOptions(slice []string) Options {
opts := make(Options)
for _, opt := range slice {
parts := strings.SplitN(opt, "=", 2)
if len(parts) == 2 {
opts[parts[0]] = parts[1]
}
}
return opts
}