create remaining tests, copy over old sam3 test

This commit is contained in:
eyedeekay
2025-10-03 11:39:19 -04:00
parent 43de76f21c
commit bb247bff70
7 changed files with 1916 additions and 0 deletions

635
compatibility_test.go Normal file
View File

@@ -0,0 +1,635 @@
package sam3
import (
"reflect"
"strings"
"testing"
)
// TestSAM3CompatibilityAPI verifies that all documented types and functions from sigs.md
// are available and have correct signatures for drop-in replacement functionality.
// This test ensures perfect API surface compatibility with the original sam3 library.
func TestSAM3CompatibilityAPI(t *testing.T) {
t.Run("CoreTypes", func(t *testing.T) {
// Verify all core types exist and are properly aliased
coreTypes := map[string]interface{}{
"SAM": (*SAM)(nil),
"StreamSession": (*StreamSession)(nil),
"DatagramSession": (*DatagramSession)(nil),
"RawSession": (*RawSession)(nil),
"PrimarySession": (*PrimarySession)(nil),
"SAMConn": (*SAMConn)(nil),
"StreamListener": (*StreamListener)(nil),
"SAMResolver": (*SAMResolver)(nil),
"I2PConfig": (*I2PConfig)(nil),
"SAMEmit": (*SAMEmit)(nil),
"Options": (*Options)(nil),
"Option": (*Option)(nil),
"BaseSession": (*BaseSession)(nil),
}
for typeName, typeValue := range coreTypes {
if typeValue == nil {
t.Errorf("Type %s should not be nil", typeName)
continue
}
// Verify type is not nil interface
typeOf := reflect.TypeOf(typeValue)
if typeOf == nil {
t.Errorf("Type %s has nil reflection type", typeName)
continue
}
t.Logf("✓ Type %s exists and is properly defined", typeName)
}
})
t.Run("Constants", func(t *testing.T) {
// Verify all signature constants exist
expectedConstants := map[string]string{
"Sig_NONE": Sig_NONE,
"Sig_DSA_SHA1": Sig_DSA_SHA1,
"Sig_ECDSA_SHA256_P256": Sig_ECDSA_SHA256_P256,
"Sig_ECDSA_SHA384_P384": Sig_ECDSA_SHA384_P384,
"Sig_ECDSA_SHA512_P521": Sig_ECDSA_SHA512_P521,
"Sig_EdDSA_SHA512_Ed25519": Sig_EdDSA_SHA512_Ed25519,
}
for constName, constValue := range expectedConstants {
if constValue == "" {
t.Errorf("Constant %s is empty", constName)
continue
}
if !strings.Contains(constValue, "SIGNATURE_TYPE=") {
t.Errorf("Constant %s does not contain expected prefix: %s", constName, constValue)
continue
}
t.Logf("✓ Constant %s = %s", constName, constValue)
}
})
t.Run("OptionVariables", func(t *testing.T) {
// Verify all option variables exist and have expected structure
optionVars := map[string][]string{
"Options_Humongous": Options_Humongous,
"Options_Large": Options_Large,
"Options_Wide": Options_Wide,
"Options_Medium": Options_Medium,
"Options_Default": Options_Default,
"Options_Small": Options_Small,
"Options_Warning_ZeroHop": Options_Warning_ZeroHop,
}
for varName, varValue := range optionVars {
if len(varValue) == 0 {
t.Errorf("Option variable %s is empty", varName)
continue
}
// Verify it contains expected tunnel options
hasInbound := false
hasOutbound := false
for _, option := range varValue {
if strings.Contains(option, "inbound.") {
hasInbound = true
}
if strings.Contains(option, "outbound.") {
hasOutbound = true
}
}
if !hasInbound || !hasOutbound {
t.Errorf("Option variable %s missing inbound/outbound options", varName)
continue
}
t.Logf("✓ Option variable %s has %d options", varName, len(varValue))
}
})
t.Run("EnvironmentVariables", func(t *testing.T) {
// Verify environment variable handling
if SAM_HOST == "" {
t.Error("SAM_HOST should have a default value")
}
if SAM_PORT == "" {
t.Error("SAM_PORT should have a default value")
}
t.Logf("✓ SAM_HOST = %s", SAM_HOST)
t.Logf("✓ SAM_PORT = %s", SAM_PORT)
})
}
// TestSAM3CompatibilityFunctions verifies that all documented functions from sigs.md
// exist and have the correct signatures for perfect drop-in replacement compatibility.
func TestSAM3CompatibilityFunctions(t *testing.T) {
t.Run("UtilityFunctions", func(t *testing.T) {
// Test utility functions exist and have correct signatures
utilityTests := []struct {
name string
test func(*testing.T)
}{
{
name: "PrimarySessionString",
test: func(t *testing.T) {
result := PrimarySessionString()
if result == "" {
t.Error("PrimarySessionString should return non-empty string")
}
t.Logf("✓ PrimarySessionString() = %s", result)
},
},
{
name: "RandString",
test: func(t *testing.T) {
result := RandString()
if len(result) == 0 {
t.Error("RandString should return non-empty string")
}
t.Logf("✓ RandString() = %s", result)
},
},
{
name: "SAMDefaultAddr",
test: func(t *testing.T) {
result := SAMDefaultAddr("")
if result == "" {
t.Error("SAMDefaultAddr should return non-empty string")
}
t.Logf("✓ SAMDefaultAddr(\"\") = %s", result)
},
},
{
name: "ExtractDest",
test: func(t *testing.T) {
input := "test-dest RESULT=OK DESTINATION=other-dest"
result := ExtractDest(input)
if result != "test-dest" {
t.Errorf("ExtractDest expected 'test-dest', got '%s'", result)
}
t.Logf("✓ ExtractDest works correctly")
},
},
{
name: "ExtractPairString",
test: func(t *testing.T) {
input := "KEY1=value1 KEY2=value2"
result := ExtractPairString(input, "KEY1")
if result != "value1" {
t.Errorf("ExtractPairString expected 'value1', got '%s'", result)
}
t.Logf("✓ ExtractPairString works correctly")
},
},
{
name: "ExtractPairInt",
test: func(t *testing.T) {
input := "PORT=7656 COUNT=10"
result := ExtractPairInt(input, "PORT")
if result != 7656 {
t.Errorf("ExtractPairInt expected 7656, got %d", result)
}
t.Logf("✓ ExtractPairInt works correctly")
},
},
}
for _, test := range utilityTests {
t.Run(test.name, test.test)
}
})
t.Run("ConstructorFunctions", func(t *testing.T) {
// Test constructor functions exist - we can't test actual functionality
// without I2P connection, but we can verify signatures
constructorTests := []struct {
name string
test func(*testing.T)
}{
{
name: "NewSAM",
test: func(t *testing.T) {
// Test that function exists and has correct signature
fnType := reflect.TypeOf(NewSAM)
if fnType.NumIn() != 1 || fnType.NumOut() != 2 {
t.Error("NewSAM should have signature: (string) (*SAM, error)")
}
t.Logf("✓ NewSAM has correct signature")
},
},
{
name: "NewSAMResolver",
test: func(t *testing.T) {
fnType := reflect.TypeOf(NewSAMResolver)
if fnType.NumIn() != 1 || fnType.NumOut() != 2 {
t.Error("NewSAMResolver should have signature: (*SAM) (*SAMResolver, error)")
}
t.Logf("✓ NewSAMResolver has correct signature")
},
},
{
name: "NewFullSAMResolver",
test: func(t *testing.T) {
fnType := reflect.TypeOf(NewFullSAMResolver)
if fnType.NumIn() != 1 || fnType.NumOut() != 2 {
t.Error("NewFullSAMResolver should have signature: (string) (*SAMResolver, error)")
}
t.Logf("✓ NewFullSAMResolver has correct signature")
},
},
}
for _, test := range constructorTests {
t.Run(test.name, test.test)
}
})
t.Run("ConfigurationFunctions", func(t *testing.T) {
// Test configuration functions exist and work correctly
configTests := []struct {
name string
test func(*testing.T)
}{
{
name: "NewConfig",
test: func(t *testing.T) {
config, err := NewConfig()
if err != nil {
t.Errorf("NewConfig() failed: %v", err)
}
if config == nil {
t.Error("NewConfig() returned nil config")
}
t.Logf("✓ NewConfig works correctly")
},
},
{
name: "NewEmit",
test: func(t *testing.T) {
emit, err := NewEmit()
if err != nil {
t.Errorf("NewEmit() failed: %v", err)
}
if emit == nil {
t.Error("NewEmit() returned nil emit")
}
t.Logf("✓ NewEmit works correctly")
},
},
{
name: "SetType",
test: func(t *testing.T) {
emit, _ := NewEmit()
err := SetType("STREAM")(emit)
if err != nil {
t.Errorf("SetType failed: %v", err)
}
if emit.Style != "STREAM" {
t.Errorf("SetType did not set style correctly")
}
t.Logf("✓ SetType works correctly")
},
},
}
for _, test := range configTests {
t.Run(test.name, test.test)
}
})
}
// TestSAM3CompatibilityIntegration tests drop-in replacement functionality with
// real I2P connections when available. These tests verify that the wrapper
// behaves identically to the original sam3 library in actual usage scenarios.
func TestSAM3CompatibilityIntegration(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration tests in short mode")
}
t.Run("BasicSAMConnection", func(t *testing.T) {
// Test basic SAM connection using sam3 API patterns
sam, err := NewSAM(SAMDefaultAddr(""))
if err != nil {
t.Skipf("Cannot connect to I2P SAM bridge: %v", err)
}
defer sam.Close()
// Verify SAM connection provides expected functionality
if sam == nil {
t.Fatal("SAM connection should not be nil")
}
// Test key generation through SAM
keys, err := sam.NewKeys()
if err != nil {
t.Fatalf("Failed to generate keys: %v", err)
}
if keys.String() == "" {
t.Fatal("Generated keys should not be empty")
}
t.Log("✓ Basic SAM connection and key generation work correctly")
})
t.Run("SessionCreationPatterns", func(t *testing.T) {
// Test common sam3 usage patterns for session creation
sam, err := NewSAM(SAMDefaultAddr(""))
if err != nil {
t.Skipf("Cannot connect to I2P SAM bridge: %v", err)
}
defer sam.Close()
keys, err := sam.NewKeys()
if err != nil {
t.Fatalf("Failed to generate keys: %v", err)
}
// Test the typical sam3 usage pattern for each session type
sessionTests := []struct {
name string
test func(*testing.T)
}{
{
name: "PrimarySessionPattern",
test: func(t *testing.T) {
session, err := sam.NewPrimarySession("compat-primary-"+RandString(), keys, Options_Default)
if err != nil {
t.Errorf("Primary session creation failed: %v", err)
return
}
defer session.Close()
// Verify primary session can create sub-sessions
if session.SubSessionCount() != 0 {
t.Error("New primary session should have 0 sub-sessions")
}
t.Log("✓ Primary session creation pattern works")
},
},
{
name: "StreamSessionPattern",
test: func(t *testing.T) {
session, err := sam.NewStreamSession("compat-stream-"+RandString(), keys, Options_Small)
if err != nil {
t.Errorf("Stream session creation failed: %v", err)
return
}
defer session.Close()
// Test listener creation - typical sam3 pattern
listener, err := session.Listen()
if err != nil {
t.Errorf("Stream listener creation failed: %v", err)
return
}
defer listener.Close()
if listener.Addr() == nil {
t.Error("Stream listener should have non-nil address")
}
t.Log("✓ Stream session creation and listener pattern works")
},
},
{
name: "DatagramSessionPattern",
test: func(t *testing.T) {
session, err := sam.NewDatagramSession("compat-datagram-"+RandString(), keys, Options_Small)
if err != nil {
t.Errorf("Datagram session creation failed: %v", err)
return
}
defer session.Close()
// Verify datagram session provides expected interface
if session.LocalAddr() == nil {
t.Error("Datagram session should have non-nil local address")
}
t.Log("✓ Datagram session creation pattern works")
},
},
{
name: "RawSessionPattern",
test: func(t *testing.T) {
session, err := sam.NewRawSession("compat-raw-"+RandString(), keys, Options_Small, 0)
if err != nil {
t.Errorf("Raw session creation failed: %v", err)
return
}
defer session.Close()
// Verify raw session provides expected interface
if session.LocalAddr() == nil {
t.Error("Raw session should have non-nil local address")
}
t.Log("✓ Raw session creation pattern works")
},
},
}
for _, test := range sessionTests {
t.Run(test.name, test.test)
}
})
t.Run("ErrorHandlingCompatibility", func(t *testing.T) {
// Test that error handling matches expected sam3 patterns
// Test invalid SAM address
_, err := NewSAM("invalid-address:999999")
if err == nil {
t.Error("Expected error for invalid SAM address")
}
t.Logf("✓ Invalid address error: %v", err)
// Test with valid connection for other error cases
sam, err := NewSAM(SAMDefaultAddr(""))
if err != nil {
t.Skipf("Cannot connect to I2P SAM bridge: %v", err)
}
defer sam.Close()
// Test invalid session type
emit, _ := NewEmit()
err = SetType("INVALID_TYPE")(emit)
if err == nil {
t.Error("Expected error for invalid session type")
}
t.Logf("✓ Invalid session type error: %v", err)
// Test invalid configuration values
err = SetInLength(-1)(emit)
if err == nil {
t.Error("Expected error for invalid tunnel length")
}
t.Logf("✓ Invalid configuration error: %v", err)
})
}
// TestSAM3CompatibilityBehavior verifies that the wrapper exhibits identical
// behavior to the original sam3 library in edge cases and specific scenarios.
func TestSAM3CompatibilityBehavior(t *testing.T) {
t.Run("AddressHandling", func(t *testing.T) {
// Test SAM address handling compatibility
tests := []struct {
name string
input string
expected string
}{
{"DefaultHost", "", SAM_HOST + ":" + SAM_PORT},
{"ExplicitAddress", "192.168.1.1:7656", SAM_HOST + ":" + SAM_PORT}, // SAMDefaultAddr ignores input when defaults are set
{"HostOnly", "localhost", SAM_HOST + ":" + SAM_PORT}, // SAMDefaultAddr ignores input when defaults are set
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result := SAMDefaultAddr(test.input)
if result != test.expected {
t.Errorf("SAMDefaultAddr(%s) = %s, expected %s", test.input, result, test.expected)
}
t.Logf("✓ SAMDefaultAddr(%s) = %s", test.input, result)
})
}
})
t.Run("StringParsing", func(t *testing.T) {
// Test string parsing functions for SAM protocol compatibility
testCases := []struct {
name string
input string
expected map[string]interface{}
}{
{
name: "DestinationExtraction",
input: "abcd1234.b32.i2p RESULT=OK VERSION=3.3",
expected: map[string]interface{}{
"dest": "abcd1234.b32.i2p",
},
},
{
name: "MultipleParams",
input: "test.b32.i2p RESULT=OK MESSAGE=Connected",
expected: map[string]interface{}{
"dest": "test.b32.i2p",
"message": "Connected",
"result": "OK",
},
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
dest := ExtractDest(test.input)
if expectedDest, ok := test.expected["dest"]; ok {
if dest != expectedDest {
t.Errorf("ExtractDest expected %s, got %s", expectedDest, dest)
}
}
message := ExtractPairString(test.input, "MESSAGE")
if expectedMsg, ok := test.expected["message"]; ok {
if message != expectedMsg {
t.Errorf("ExtractPairString(MESSAGE) expected %s, got %s", expectedMsg, message)
}
}
t.Logf("✓ String parsing works correctly for %s", test.name)
})
}
})
t.Run("ConfigurationBehavior", func(t *testing.T) {
// Test configuration behavior matches sam3 expectations
emit, _ := NewEmit()
// Test option string generation
options := []string{"inbound.length=3", "outbound.length=3"}
optString := GenerateOptionString(options)
if !strings.Contains(optString, "inbound.length=3") {
t.Error("Generated option string should contain inbound.length=3")
}
if !strings.Contains(optString, "outbound.length=3") {
t.Error("Generated option string should contain outbound.length=3")
}
// Test configuration chaining
err := SetType("STREAM")(emit)
if err != nil {
t.Errorf("Configuration chaining failed: %v", err)
}
err = SetSAMHost("127.0.0.1")(emit)
if err != nil {
t.Errorf("Configuration chaining failed: %v", err)
}
if emit.Style != "STREAM" {
t.Error("Configuration chaining did not preserve previous settings")
}
if emit.I2PConfig.SamHost != "127.0.0.1" {
t.Error("Configuration chaining did not apply new settings")
}
t.Log("✓ Configuration behavior matches sam3 patterns")
})
}
// BenchmarkSAM3Compatibility benchmarks critical operations to ensure
// performance is comparable to original sam3 library expectations.
func BenchmarkSAM3Compatibility(b *testing.B) {
b.Run("UtilityFunctions", func(b *testing.B) {
b.Run("RandString", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = RandString()
}
})
b.Run("SAMDefaultAddr", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = SAMDefaultAddr("")
}
})
b.Run("ExtractDest", func(b *testing.B) {
input := "HELLO REPLY RESULT=OK DESTINATION=test.b32.i2p"
for i := 0; i < b.N; i++ {
_ = ExtractDest(input)
}
})
b.Run("GenerateOptionString", func(b *testing.B) {
options := Options_Default
for i := 0; i < b.N; i++ {
_ = GenerateOptionString(options)
}
})
})
b.Run("Configuration", func(b *testing.B) {
b.Run("NewEmit", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = NewEmit()
}
})
b.Run("SetType", func(b *testing.B) {
emit, _ := NewEmit()
for i := 0; i < b.N; i++ {
_ = SetType("STREAM")(emit)
}
})
b.Run("OptionVariableAccess", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = Options_Default
}
})
})
}

131
datagram_test.go Normal file
View File

@@ -0,0 +1,131 @@
package sam3
import (
"fmt"
"testing"
"time"
)
func Test_DatagramServerClient(t *testing.T) {
if testing.Short() {
return
}
fmt.Println("Test_DatagramServerClient")
sam, err := NewSAM(SAMDefaultAddr(""))
if err != nil {
t.Fail()
return
}
defer sam.Close()
keys, err := sam.NewKeys()
if err != nil {
t.Fail()
return
}
// fmt.Println("\tServer: My address: " + keys.Addr().Base32())
fmt.Println("\tServer: Creating tunnel")
ds, err := sam.NewDatagramSession("DGserverTun", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"}, 0)
if err != nil {
fmt.Println("Server: Failed to create tunnel: " + err.Error())
t.Fail()
return
}
c, w := make(chan bool), make(chan bool)
go func(c, w chan (bool)) {
sam2, err := NewSAM(SAMDefaultAddr(""))
if err != nil {
c <- false
return
}
defer sam2.Close()
keys, err := sam2.NewKeys()
if err != nil {
c <- false
return
}
fmt.Println("\tClient: Creating tunnel")
ds2, err := sam2.NewDatagramSession("DGclientTun", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"}, 0)
if err != nil {
c <- false
return
}
defer ds2.Close()
// fmt.Println("\tClient: Servers address: " + ds.LocalAddr().Base32())
// fmt.Println("\tClient: Clients address: " + ds2.LocalAddr().Base32())
fmt.Println("\tClient: Tries to send datagram to server")
for {
select {
default:
_, err = ds2.WriteTo([]byte("Hello datagram-world! <3 <3 <3 <3 <3 <3"), ds.LocalAddr())
if err != nil {
fmt.Println("\tClient: Failed to send datagram: " + err.Error())
c <- false
return
}
time.Sleep(5 * time.Second)
case <-w:
fmt.Println("\tClient: Sent datagram, quitting.")
return
}
}
c <- true
}(c, w)
buf := make([]byte, 512)
fmt.Println("\tServer: ReadFrom() waiting...")
n, _, err := ds.ReadFrom(buf)
w <- true
if err != nil {
fmt.Println("\tServer: Failed to ReadFrom(): " + err.Error())
t.Fail()
return
}
fmt.Println("\tServer: Received datagram: " + string(buf[:n]))
// fmt.Println("\tServer: Senders address was: " + saddr.Base32())
}
func ExampleDatagramSession() {
// Creates a new DatagramSession, which behaves just like a net.PacketConn.
const samBridge = "127.0.0.1:7656"
sam, err := NewSAM(samBridge)
if err != nil {
fmt.Println(err.Error())
return
}
keys, err := sam.NewKeys()
if err != nil {
fmt.Println(err.Error())
return
}
myself := keys.Addr()
// See the example Option_* variables.
dg, err := sam.NewDatagramSession("DGTUN", keys, Options_Small, 0)
if err != nil {
fmt.Println(err.Error())
return
}
someone, err := sam.Lookup("zzz.i2p")
if err != nil {
fmt.Println(err.Error())
return
}
dg.WriteTo([]byte("Hello stranger!"), someone)
dg.WriteTo([]byte("Hello myself!"), myself)
buf := make([]byte, 31*1024)
n, _, err := dg.ReadFrom(buf)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println("Got message: '" + string(buf[:n]) + "'")
fmt.Println("Got message: " + string(buf[:n]))
return
// Output:
//Got message: Hello myself!
}

235
example_test.go Normal file
View File

@@ -0,0 +1,235 @@
package sam3_test
import (
"fmt"
"log"
sam3 "github.com/go-i2p/go-sam-go"
)
// Example demonstrates basic usage of the sam3 library for I2P connectivity.
// This example shows how to establish a SAM connection, generate keys, and create sessions.
func Example() {
// Connect to the local I2P SAM bridge
sam, err := sam3.NewSAM("127.0.0.1:7656")
if err != nil {
log.Printf("Cannot connect to I2P: %v", err)
return
}
defer sam.Close()
// Generate I2P keys for this session
keys, err := sam.NewKeys()
if err != nil {
log.Printf("Failed to generate keys: %v", err)
return
}
// Create a stream session for TCP-like connections
session, err := sam.NewStreamSession("example-session", keys, sam3.Options_Default)
if err != nil {
log.Printf("Failed to create session: %v", err)
return
}
defer session.Close()
fmt.Println("Successfully connected to I2P and created a session")
// Output: Successfully connected to I2P and created a session
}
// ExampleNewSAM demonstrates how to establish a connection to the I2P SAM bridge.
func ExampleNewSAM() {
// Connect to the default I2P SAM bridge address
sam, err := sam3.NewSAM(sam3.SAMDefaultAddr(""))
if err != nil {
log.Printf("Cannot connect to I2P: %v", err)
return
}
defer sam.Close()
fmt.Println("Connected to I2P SAM bridge")
}
// ExampleSAM_NewStreamSession demonstrates creating a stream session for reliable connections.
func ExampleSAM_NewStreamSession() {
sam, err := sam3.NewSAM("127.0.0.1:7656")
if err != nil {
log.Printf("Cannot connect to I2P: %v", err)
return
}
defer sam.Close()
keys, err := sam.NewKeys()
if err != nil {
log.Printf("Failed to generate keys: %v", err)
return
}
// Create a stream session with default tunnel configuration
session, err := sam.NewStreamSession("my-app", keys, sam3.Options_Default)
if err != nil {
log.Printf("Failed to create stream session: %v", err)
return
}
defer session.Close()
fmt.Println("Stream session created successfully")
}
// ExampleSAM_NewPrimarySession demonstrates creating a primary session for managing sub-sessions.
func ExampleSAM_NewPrimarySession() {
sam, err := sam3.NewSAM("127.0.0.1:7656")
if err != nil {
log.Printf("Cannot connect to I2P: %v", err)
return
}
defer sam.Close()
keys, err := sam.NewKeys()
if err != nil {
log.Printf("Failed to generate keys: %v", err)
return
}
// Create a primary session that can manage multiple sub-sessions
primary, err := sam.NewPrimarySession("master-session", keys, sam3.Options_Medium)
if err != nil {
log.Printf("Failed to create primary session: %v", err)
return
}
defer primary.Close()
fmt.Printf("Primary session created with %d sub-sessions", primary.SubSessionCount())
// Output: Primary session created with 0 sub-sessions
}
// ExampleSAM_NewDatagramSession demonstrates creating a datagram session for UDP-like messaging.
func ExampleSAM_NewDatagramSession() {
sam, err := sam3.NewSAM("127.0.0.1:7656")
if err != nil {
log.Printf("Cannot connect to I2P: %v", err)
return
}
defer sam.Close()
keys, err := sam.NewKeys()
if err != nil {
log.Printf("Failed to generate keys: %v", err)
return
}
// Create a datagram session for authenticated messaging
session, err := sam.NewDatagramSession("udp-app", keys, sam3.Options_Small)
if err != nil {
log.Printf("Failed to create datagram session: %v", err)
return
}
defer session.Close()
fmt.Println("Datagram session created successfully")
}
// ExampleOptions demonstrates using predefined tunnel configuration options.
func ExampleOptions() {
// Use predefined options for different traffic patterns
// For applications with heavy traffic
heavyTrafficOptions := sam3.Options_Large
// For applications with medium traffic (most common)
normalOptions := sam3.Options_Default
// For lightweight applications
lightOptions := sam3.Options_Small
// For maximum anonymity with very heavy traffic
maxAnonOptions := sam3.Options_Humongous
fmt.Printf("Heavy: %d options, Normal: %d options, Light: %d options, Max Anon: %d options",
len(heavyTrafficOptions), len(normalOptions), len(lightOptions), len(maxAnonOptions))
// Output: Heavy: 8 options, Normal: 8 options, Light: 8 options, Max Anon: 8 options
}
// ExampleRandString demonstrates generating random session identifiers.
func ExampleRandString() {
// Generate random strings for session IDs
sessionID := sam3.RandString()
fmt.Printf("Generated session ID length: %d", len(sessionID))
// Output: Generated session ID length: 12
}
// ExampleSAMDefaultAddr demonstrates using the default SAM address with environment variable support.
func ExampleSAMDefaultAddr() {
// Get default SAM address (uses environment variables if set)
defaultAddr := sam3.SAMDefaultAddr("")
fmt.Printf("Default SAM address: %s", defaultAddr)
// Output: Default SAM address: 127.0.0.1:7656
}
// ExampleExtractDest demonstrates extracting destinations from SAM protocol strings.
func ExampleExtractDest() {
// Extract the first word (destination) from a SAM response
response := "abc123.b32.i2p RESULT=OK VERSION=3.3"
dest := sam3.ExtractDest(response)
fmt.Printf("Extracted destination: %s", dest)
// Output: Extracted destination: abc123.b32.i2p
}
// ExampleExtractPairString demonstrates extracting string values from SAM protocol responses.
func ExampleExtractPairString() {
// Extract specific parameters from SAM responses
response := "RESULT=OK MESSAGE=Connected VERSION=3.3"
result := sam3.ExtractPairString(response, "RESULT")
message := sam3.ExtractPairString(response, "MESSAGE")
fmt.Printf("Result: %s, Message: %s", result, message)
// Output: Result: OK, Message: Connected
}
// ExampleExtractPairInt demonstrates extracting integer values from SAM protocol responses.
func ExampleExtractPairInt() {
// Extract numeric parameters from SAM responses
response := "RESULT=OK PORT=7656 COUNT=5"
port := sam3.ExtractPairInt(response, "PORT")
count := sam3.ExtractPairInt(response, "COUNT")
fmt.Printf("Port: %d, Count: %d", port, count)
// Output: Port: 7656, Count: 5
}
// ExampleSetType demonstrates configuring session types using the functional options pattern.
func ExampleSetType() {
// Create a new SAM configuration
emit, err := sam3.NewEmit(
sam3.SetType("STREAM"),
sam3.SetSAMHost("127.0.0.1"),
sam3.SetSAMPort("7656"),
)
if err != nil {
log.Printf("Configuration failed: %v", err)
return
}
fmt.Printf("Configured session type: %s", emit.Style)
// Output: Configured session type: STREAM
}
// ExampleNewEmit demonstrates creating SAM configuration with functional options.
func ExampleNewEmit() {
// Create configuration with multiple options
config, err := sam3.NewEmit(
sam3.SetType("DATAGRAM"),
sam3.SetInLength(2),
sam3.SetOutLength(2),
sam3.SetInQuantity(3),
sam3.SetOutQuantity(3),
)
if err != nil {
log.Printf("Configuration failed: %v", err)
return
}
fmt.Printf("Session type: %s, Tunnels: in=%d out=%d",
config.Style, config.I2PConfig.InQuantity, config.I2PConfig.OutQuantity)
// Output: Session type: DATAGRAM, Tunnels: in=3 out=3
}

93
primary_datagram_test.go Normal file
View File

@@ -0,0 +1,93 @@
package sam3
import (
"fmt"
"testing"
"time"
)
func Test_PrimaryDatagramServerClient(t *testing.T) {
if testing.Short() {
return
}
fmt.Println("Test_PrimaryDatagramServerClient")
earlysam, err := NewSAM(SAMDefaultAddr(""))
if err != nil {
t.Fail()
return
}
defer earlysam.Close()
keys, err := earlysam.NewKeys()
if err != nil {
t.Fail()
return
}
sam, err := earlysam.NewPrimarySession("PrimaryTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
if err != nil {
t.Fail()
return
}
defer sam.Close()
// fmt.Println("\tServer: My address: " + keys.Addr().Base32())
fmt.Println("\tServer: Creating tunnel")
ds, err := sam.NewDatagramSubSession("PrimaryTunnel"+RandString(), 0)
if err != nil {
fmt.Println("Server: Failed to create tunnel: " + err.Error())
t.Fail()
return
}
defer ds.Close()
c, w := make(chan bool), make(chan bool)
go func(c, w chan (bool)) {
sam2, err := NewSAM(SAMDefaultAddr(""))
if err != nil {
c <- false
return
}
defer sam2.Close()
keys, err := sam2.NewKeys()
if err != nil {
c <- false
return
}
fmt.Println("\tClient: Creating tunnel")
ds2, err := sam2.NewDatagramSession("PRIMARYClientTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"}, 0)
if err != nil {
c <- false
return
}
defer ds2.Close()
// fmt.Println("\tClient: Servers address: " + ds.LocalAddr().Base32())
// fmt.Println("\tClient: Clients address: " + ds2.LocalAddr().Base32())
fmt.Println("\tClient: Tries to send primary to server")
for {
select {
default:
_, err = ds2.WriteTo([]byte("Hello primary-world! <3 <3 <3 <3 <3 <3"), ds.LocalAddr())
if err != nil {
fmt.Println("\tClient: Failed to send primary: " + err.Error())
c <- false
return
}
time.Sleep(5 * time.Second)
case <-w:
fmt.Println("\tClient: Sent primary, quitting.")
return
}
}
c <- true
}(c, w)
buf := make([]byte, 512)
fmt.Println("\tServer: ReadFrom() waiting...")
n, _, err := ds.ReadFrom(buf)
w <- true
if err != nil {
fmt.Println("\tServer: Failed to ReadFrom(): " + err.Error())
t.Fail()
return
}
fmt.Println("\tServer: Received primary: " + string(buf[:n]))
// fmt.Println("\tServer: Senders address was: " + saddr.Base32())
}

183
primary_stream_test.go Normal file
View File

@@ -0,0 +1,183 @@
package sam3
import (
"fmt"
"net/http"
"strings"
"testing"
"time"
)
/*
* This file contains tests and examples for the primary stream session functionality of the sam3 package.
* It was copied directly from the sam3 package and modified to fit the current context.
* The tests cover creating primary stream sessions, dialing I2P addresses, and establishing
* server-client communication over I2P streams. Examples demonstrate basic usage of
* the sam3 library for connecting to I2P and creating primary stream sessions.
*
* Note: These tests require a running I2P router with SAM bridge enabled.
*/
func Test_PrimaryStreamingDial(t *testing.T) {
if testing.Short() {
return
}
fmt.Println("Test_PrimaryStreamingDial")
earlysam, err := NewSAM(SAMDefaultAddr(""))
if err != nil {
t.Fail()
return
}
defer earlysam.Close()
keys, err := earlysam.NewKeys()
if err != nil {
t.Fail()
return
}
sam, err := earlysam.NewPrimarySession("PrimaryTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
if err != nil {
t.Fail()
return
}
defer sam.Close()
fmt.Println("\tBuilding tunnel")
ss, err := sam.NewStreamSubSession("primaryStreamTunnel")
if err != nil {
fmt.Println(err.Error())
t.Fail()
return
}
defer ss.Close()
fmt.Println("\tNotice: This may fail if your I2P node is not well integrated in the I2P network.")
fmt.Println("\tLooking up i2p-projekt.i2p")
forumAddr, err := earlysam.Lookup("i2p-projekt.i2p")
if err != nil {
fmt.Println(err.Error())
t.Fail()
return
}
fmt.Println("\tDialing i2p-projekt.i2p(", forumAddr.Base32(), forumAddr.DestHash().Hash(), ")")
conn, err := ss.DialI2P(forumAddr)
if err != nil {
fmt.Println(err.Error())
t.Fail()
return
}
defer conn.Close()
fmt.Println("\tSending HTTP GET /")
if _, err := conn.Write([]byte("GET /\n")); err != nil {
fmt.Println(err.Error())
t.Fail()
return
}
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if !strings.Contains(strings.ToLower(string(buf[:n])), "http") && !strings.Contains(strings.ToLower(string(buf[:n])), "html") {
fmt.Printf("\tProbably failed to StreamSession.DialI2P(i2p-projekt.i2p)? It replied %d bytes, but nothing that looked like http/html", n)
} else {
fmt.Println("\tRead HTTP/HTML from i2p-projekt.i2p")
}
}
func Test_PrimaryStreamingServerClient(t *testing.T) {
if testing.Short() {
return
}
fmt.Println("Test_StreamingServerClient")
earlysam, err := NewSAM(SAMDefaultAddr(""))
if err != nil {
t.Fail()
return
}
defer earlysam.Close()
keys, err := earlysam.NewKeys()
if err != nil {
t.Fail()
return
}
sam, err := earlysam.NewPrimarySession("PrimaryServerClientTunnel", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
if err != nil {
t.Fail()
return
}
defer sam.Close()
fmt.Println("\tServer: Creating tunnel")
ss, err := sam.NewUniqueStreamSubSession("PrimaryServerClientTunnel")
if err != nil {
return
}
defer ss.Close()
time.Sleep(time.Second * 10)
c, w := make(chan bool), make(chan bool)
go func(c, w chan (bool)) {
if !(<-w) {
return
}
/*
sam2, err := NewSAM(SAMDefaultAddr(""))
if err != nil {
c <- false
return
}
defer sam2.Close()
keys, err := sam2.NewKeys()
if err != nil {
c <- false
return
}
*/
fmt.Println("\tClient: Creating tunnel")
ss2, err := sam.NewStreamSubSession("primaryExampleClientTun")
if err != nil {
c <- false
return
}
defer ss2.Close()
fmt.Println("\tClient: Connecting to server")
conn, err := ss2.DialI2P(ss.Addr())
if err != nil {
c <- false
return
}
fmt.Println("\tClient: Connected to tunnel")
defer conn.Close()
_, err = conn.Write([]byte("Hello world <3 <3 <3 <3 <3 <3"))
if err != nil {
c <- false
return
}
c <- true
}(c, w)
l, err := ss.Listen()
if err != nil {
fmt.Println("ss.Listen(): " + err.Error())
t.Fail()
w <- false
return
}
defer l.Close()
w <- true
fmt.Println("\tServer: Accept()ing on tunnel")
conn, err := l.Accept()
if err != nil {
t.Fail()
fmt.Println("Failed to Accept(): " + err.Error())
return
}
defer conn.Close()
buf := make([]byte, 512)
n, err := conn.Read(buf)
fmt.Printf("\tClient exited successfully: %t\n", <-c)
fmt.Println("\tServer: received from Client: " + string(buf[:n]))
}
type exitHandler struct {
}
func (e *exitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello world!"))
}

345
signature_test.go Normal file
View File

@@ -0,0 +1,345 @@
package sam3
import (
"go/ast"
"go/parser"
"go/token"
"reflect"
"strings"
"testing"
)
// TestSAM3SignatureCompatibility verifies that all function signatures exactly match
// the specifications in sigs.md for perfect drop-in replacement compatibility.
func TestSAM3SignatureCompatibility(t *testing.T) {
t.Run("SessionCreationSignatures", func(t *testing.T) {
// Expected signatures from sigs.md
expectedSignatures := map[string]string{
"NewDatagramSession": "func (s *SAM) NewDatagramSession(id string, keys i2pkeys.I2PKeys, options []string, udpPort int) (*DatagramSession, error)",
"NewPrimarySession": "func (sam *SAM) NewPrimarySession(id string, keys i2pkeys.I2PKeys, options []string) (*PrimarySession, error)",
"NewPrimarySessionWithSignature": "func (sam *SAM) NewPrimarySessionWithSignature(id string, keys i2pkeys.I2PKeys, options []string, sigType string) (*PrimarySession, error)",
"NewRawSession": "func (s *SAM) NewRawSession(id string, keys i2pkeys.I2PKeys, options []string, udpPort int) (*RawSession, error)",
"NewStreamSession": "func (sam *SAM) NewStreamSession(id string, keys i2pkeys.I2PKeys, options []string) (*StreamSession, error)",
"NewStreamSessionWithSignature": "func (sam *SAM) NewStreamSessionWithSignature(id string, keys i2pkeys.I2PKeys, options []string, sigType string) (*StreamSession, error)",
"NewStreamSessionWithSignatureAndPorts": "func (sam *SAM) NewStreamSessionWithSignatureAndPorts(id, from, to string, keys i2pkeys.I2PKeys, options []string, sigType string) (*StreamSession, error)",
}
// Get the actual SAM type
samType := reflect.TypeOf(&SAM{})
for methodName, _ := range expectedSignatures {
method, exists := samType.MethodByName(methodName)
if !exists {
t.Errorf("Method %s not found on SAM type", methodName)
continue
}
// Verify method exists and is callable
if method.Type.NumIn() == 0 {
t.Errorf("Method %s has no input parameters", methodName)
continue
}
// Verify first parameter is receiver (*SAM)
if method.Type.In(0) != samType {
t.Errorf("Method %s receiver is not *SAM", methodName)
continue
}
t.Logf("✓ Method %s exists with correct receiver", methodName)
}
})
t.Run("UtilityFunctionSignatures", func(t *testing.T) {
// Test utility functions have expected signatures
utilityTests := []struct {
name string
function interface{}
inputs int
outputs int
}{
{"NewSAM", NewSAM, 1, 2},
{"NewSAMResolver", NewSAMResolver, 1, 2},
{"NewFullSAMResolver", NewFullSAMResolver, 1, 2},
{"RandString", RandString, 0, 1},
{"SAMDefaultAddr", SAMDefaultAddr, 1, 1},
{"ExtractDest", ExtractDest, 1, 1},
{"ExtractPairString", ExtractPairString, 2, 1},
{"ExtractPairInt", ExtractPairInt, 2, 1},
{"GenerateOptionString", GenerateOptionString, 1, 1},
{"PrimarySessionString", PrimarySessionString, 0, 1},
}
for _, test := range utilityTests {
t.Run(test.name, func(t *testing.T) {
funcType := reflect.TypeOf(test.function)
if funcType.Kind() != reflect.Func {
t.Errorf("%s is not a function", test.name)
return
}
if funcType.NumIn() != test.inputs {
t.Errorf("%s expected %d inputs, got %d", test.name, test.inputs, funcType.NumIn())
return
}
if funcType.NumOut() != test.outputs {
t.Errorf("%s expected %d outputs, got %d", test.name, test.outputs, funcType.NumOut())
return
}
t.Logf("✓ %s has correct signature (%d inputs, %d outputs)", test.name, test.inputs, test.outputs)
})
}
})
t.Run("ConfigurationFunctionSignatures", func(t *testing.T) {
// Test configuration functions have functional options pattern
configTests := []struct {
name string
function interface{}
}{
{"SetType", SetType},
{"SetSAMHost", SetSAMHost},
{"SetSAMPort", SetSAMPort},
{"SetName", SetName},
{"SetInLength", SetInLength},
{"SetOutLength", SetOutLength},
{"SetInQuantity", SetInQuantity},
{"SetOutQuantity", SetOutQuantity},
{"SetInBackups", SetInBackups},
{"SetOutBackups", SetOutBackups},
{"SetEncrypt", SetEncrypt},
{"SetCompress", SetCompress},
}
for _, test := range configTests {
t.Run(test.name, func(t *testing.T) {
funcType := reflect.TypeOf(test.function)
if funcType.Kind() != reflect.Func {
t.Errorf("%s is not a function", test.name)
return
}
// Configuration functions should take 1 input and return a function
if funcType.NumIn() != 1 {
t.Errorf("%s expected 1 input parameter", test.name)
return
}
if funcType.NumOut() != 1 {
t.Errorf("%s expected 1 output parameter", test.name)
return
}
// Output should be a function type
outputType := funcType.Out(0)
if outputType.Kind() != reflect.Func {
t.Errorf("%s should return a function", test.name)
return
}
t.Logf("✓ %s follows functional options pattern", test.name)
})
}
})
t.Run("TypeDefinitionCompatibility", func(t *testing.T) {
// Verify type aliases point to correct underlying types
typeTests := []struct {
name string
aliasType interface{}
description string
}{
{"SAM", (*SAM)(nil), "Core SAM connection type"},
{"StreamSession", (*StreamSession)(nil), "TCP-like session type"},
{"DatagramSession", (*DatagramSession)(nil), "UDP-like session type"},
{"RawSession", (*RawSession)(nil), "Anonymous datagram session type"},
{"PrimarySession", (*PrimarySession)(nil), "Multi-session management type"},
{"SAMConn", (*SAMConn)(nil), "Stream connection type"},
{"StreamListener", (*StreamListener)(nil), "Stream listener type"},
{"I2PConfig", (*I2PConfig)(nil), "I2P configuration type"},
{"SAMEmit", (*SAMEmit)(nil), "SAM emission configuration type"},
}
for _, test := range typeTests {
t.Run(test.name, func(t *testing.T) {
typeOf := reflect.TypeOf(test.aliasType)
if typeOf == nil {
t.Errorf("Type %s is nil", test.name)
return
}
// Verify it's a pointer to a struct (expected for these types)
if typeOf.Kind() != reflect.Ptr {
t.Errorf("Type %s should be a pointer type", test.name)
return
}
elem := typeOf.Elem()
if elem.Kind() != reflect.Struct {
t.Errorf("Type %s should point to a struct", test.name)
return
}
t.Logf("✓ Type %s: %s", test.name, test.description)
})
}
})
}
// TestSAM3PackageStructure verifies the package exports match sigs.md expectations.
func TestSAM3PackageStructure(t *testing.T) {
t.Run("PackageExports", func(t *testing.T) {
// Parse the package to get actual exports
fset := token.NewFileSet()
packages, err := parser.ParseDir(fset, ".", nil, parser.ParseComments)
if err != nil {
t.Fatalf("Failed to parse package: %v", err)
}
sam3Pkg, exists := packages["sam3"]
if !exists {
t.Fatal("sam3 package not found")
}
// Collect exported identifiers
exports := make(map[string]bool)
for _, file := range sam3Pkg.Files {
ast.Inspect(file, func(n ast.Node) bool {
switch node := n.(type) {
case *ast.FuncDecl:
if node.Name.IsExported() {
exports[node.Name.Name] = true
}
case *ast.TypeSpec:
if node.Name.IsExported() {
exports[node.Name.Name] = true
}
case *ast.ValueSpec:
for _, name := range node.Names {
if name.IsExported() {
exports[name.Name] = true
}
}
}
return true
})
}
// Expected major exports from sigs.md
expectedExports := []string{
// Types
"SAM", "StreamSession", "DatagramSession", "RawSession", "PrimarySession",
"SAMConn", "StreamListener", "SAMResolver", "I2PConfig", "SAMEmit",
// Constants
"Sig_NONE", "Sig_DSA_SHA1", "Sig_ECDSA_SHA256_P256", "Sig_ECDSA_SHA384_P384",
"Sig_ECDSA_SHA512_P521", "Sig_EdDSA_SHA512_Ed25519",
// Variables
"Options_Humongous", "Options_Large", "Options_Wide", "Options_Medium",
"Options_Default", "Options_Small", "Options_Warning_ZeroHop",
"SAM_HOST", "SAM_PORT",
// Functions
"NewSAM", "NewSAMResolver", "NewFullSAMResolver", "RandString",
"SAMDefaultAddr", "ExtractDest", "ExtractPairString", "ExtractPairInt",
}
for _, expected := range expectedExports {
if !exports[expected] {
t.Errorf("Expected export %s not found", expected)
} else {
t.Logf("✓ Export %s found", expected)
}
}
t.Logf("Package exports %d identifiers total", len(exports))
})
t.Run("ImportCompatibility", func(t *testing.T) {
// Verify the package can be imported as expected
// This test ensures the package structure allows drop-in replacement
// Check that main types are available for type assertions
var sam *SAM
var streamSession *StreamSession
var datagramSession *DatagramSession
var rawSession *RawSession
var primarySession *PrimarySession
// Verify interfaces work as expected
if sam != nil || streamSession != nil || datagramSession != nil ||
rawSession != nil || primarySession != nil {
// This is just for compilation checking
}
// Verify constants are accessible
signatures := []string{
Sig_NONE, Sig_DSA_SHA1, Sig_ECDSA_SHA256_P256,
Sig_ECDSA_SHA384_P384, Sig_ECDSA_SHA512_P521, Sig_EdDSA_SHA512_Ed25519,
}
if len(signatures) != 6 {
t.Error("Not all signature constants are accessible")
}
// Verify option variables are accessible
options := [][]string{
Options_Humongous, Options_Large, Options_Wide,
Options_Medium, Options_Default, Options_Small, Options_Warning_ZeroHop,
}
if len(options) != 7 {
t.Error("Not all option variables are accessible")
}
t.Log("✓ Package structure supports drop-in replacement")
})
}
// TestSAM3DocumentationCompleteness verifies that all public functions have adequate documentation.
func TestSAM3DocumentationCompleteness(t *testing.T) {
t.Run("FunctionDocumentation", func(t *testing.T) {
// Parse the package to check documentation
fset := token.NewFileSet()
packages, err := parser.ParseDir(fset, ".", nil, parser.ParseComments)
if err != nil {
t.Fatalf("Failed to parse package: %v", err)
}
sam3Pkg, exists := packages["sam3"]
if !exists {
t.Fatal("sam3 package not found")
}
undocumentedFunctions := []string{}
// Check function documentation
for _, file := range sam3Pkg.Files {
ast.Inspect(file, func(n ast.Node) bool {
switch node := n.(type) {
case *ast.FuncDecl:
if node.Name.IsExported() {
// Check if function has documentation
if node.Doc == nil || len(node.Doc.List) == 0 {
undocumentedFunctions = append(undocumentedFunctions, node.Name.Name)
} else {
// Check if documentation starts with function name
firstLine := node.Doc.List[0].Text
if !strings.Contains(firstLine, node.Name.Name) {
t.Logf("Warning: %s documentation may not follow Go conventions", node.Name.Name)
}
}
}
}
return true
})
}
if len(undocumentedFunctions) > 0 {
t.Logf("Functions without documentation: %v", undocumentedFunctions)
// We'll log but not fail for documentation, as some may be aliases
}
t.Logf("✓ Documentation completeness check completed")
})
}

294
stream_test.go Normal file
View File

@@ -0,0 +1,294 @@
package sam3
import (
"fmt"
"log"
"strings"
"testing"
"github.com/go-i2p/i2pkeys"
)
/*
* This file contains tests and examples for the stream session functionality of the sam3 package.
* It was copied directly from the sam3 package and modified to fit the current context.
* The tests cover creating stream sessions, dialing I2P addresses, and establishing
* server-client communication over I2P streams. Examples demonstrate basic usage of
* the sam3 library for connecting to I2P and creating stream sessions.
*
* Note: These tests require a running I2P router with SAM bridge enabled.
*/
func Test_StreamingDial(t *testing.T) {
if testing.Short() {
return
}
fmt.Println("Test_StreamingDial")
sam, err := NewSAM(SAMDefaultAddr(""))
if err != nil {
fmt.Println(err.Error())
t.Fail()
return
}
defer sam.Close()
keys, err := sam.NewKeys()
if err != nil {
fmt.Println(err.Error())
t.Fail()
return
}
fmt.Println("\tBuilding tunnel")
ss, err := sam.NewStreamSession("streamTun", keys, []string{"inbound.length=1", "outbound.length=1", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
if err != nil {
fmt.Println(err.Error())
t.Fail()
return
}
fmt.Println("\tNotice: This may fail if your I2P node is not well integrated in the I2P network.")
fmt.Println("\tLooking up i2p-projekt.i2p")
forumAddr, err := sam.Lookup("i2p-projekt.i2p")
if err != nil {
fmt.Println(err.Error())
t.Fail()
return
}
fmt.Println("\tDialing i2p-projekt.i2p(", forumAddr.Base32(), forumAddr.DestHash().Hash(), ")")
conn, err := ss.DialI2P(forumAddr)
if err != nil {
fmt.Println(err.Error())
t.Fail()
return
}
defer conn.Close()
fmt.Println("\tSending HTTP GET /")
if _, err := conn.Write([]byte("GET /\n")); err != nil {
fmt.Println(err.Error())
t.Fail()
return
}
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if !strings.Contains(strings.ToLower(string(buf[:n])), "http") && !strings.Contains(strings.ToLower(string(buf[:n])), "html") {
fmt.Printf("\tProbably failed to StreamSession.DialI2P(i2p-projekt.i2p)? It replied %d bytes, but nothing that looked like http/html", n)
} else {
fmt.Println("\tRead HTTP/HTML from i2p-projekt.i2p")
}
}
func Test_StreamingServerClient(t *testing.T) {
if testing.Short() {
return
}
fmt.Println("Test_StreamingServerClient")
sam, err := NewSAM(SAMDefaultAddr(""))
if err != nil {
t.Fail()
return
}
defer sam.Close()
keys, err := sam.NewKeys()
if err != nil {
t.Fail()
return
}
fmt.Println("\tServer: Creating tunnel")
ss, err := sam.NewStreamSession("serverTun", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
if err != nil {
return
}
c, w := make(chan bool), make(chan bool)
go func(c, w chan (bool)) {
if !(<-w) {
return
}
sam2, err := NewSAM(SAMDefaultAddr(""))
if err != nil {
c <- false
return
}
defer sam2.Close()
keys, err := sam2.NewKeys()
if err != nil {
c <- false
return
}
fmt.Println("\tClient: Creating tunnel")
ss2, err := sam2.NewStreamSession("clientTun", keys, []string{"inbound.length=0", "outbound.length=0", "inbound.lengthVariance=0", "outbound.lengthVariance=0", "inbound.quantity=1", "outbound.quantity=1"})
if err != nil {
c <- false
return
}
fmt.Println("\tClient: Connecting to server")
conn, err := ss2.DialI2P(ss.Addr())
if err != nil {
c <- false
return
}
fmt.Println("\tClient: Connected to tunnel")
defer conn.Close()
_, err = conn.Write([]byte("Hello world <3 <3 <3 <3 <3 <3"))
if err != nil {
c <- false
return
}
c <- true
}(c, w)
l, err := ss.Listen()
if err != nil {
fmt.Println("ss.Listen(): " + err.Error())
t.Fail()
w <- false
return
}
defer l.Close()
w <- true
fmt.Println("\tServer: Accept()ing on tunnel")
conn, err := l.Accept()
if err != nil {
t.Fail()
fmt.Println("Failed to Accept(): " + err.Error())
return
}
defer conn.Close()
buf := make([]byte, 512)
n, err := conn.Read(buf)
fmt.Printf("\tClient exited successfully: %t\n", <-c)
fmt.Println("\tServer: received from Client: " + string(buf[:n]))
}
func ExampleStreamSession() {
// Creates a new StreamingSession, dials to idk.i2p and gets a SAMConn
// which behaves just like a normal net.Conn.
const samBridge = "127.0.0.1:7656"
sam, err := NewSAM(samBridge)
if err != nil {
fmt.Println(err.Error())
return
}
defer sam.Close()
keys, err := sam.NewKeys()
if err != nil {
fmt.Println(err.Error())
return
}
// See the example Option_* variables.
ss, err := sam.NewStreamSession("stream_example", keys, Options_Small)
if err != nil {
fmt.Println(err.Error())
return
}
someone, err := sam.Lookup("idk.i2p")
if err != nil {
fmt.Println(err.Error())
return
}
conn, err := ss.DialI2P(someone)
if err != nil {
fmt.Println(err.Error())
return
}
defer conn.Close()
fmt.Println("Sending HTTP GET /")
if _, err := conn.Write([]byte("GET /\n")); err != nil {
fmt.Println(err.Error())
return
}
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if !strings.Contains(strings.ToLower(string(buf[:n])), "http") && !strings.Contains(strings.ToLower(string(buf[:n])), "html") {
fmt.Printf("Probably failed to StreamSession.DialI2P(idk.i2p)? It replied %d bytes, but nothing that looked like http/html", n)
log.Printf("Probably failed to StreamSession.DialI2P(idk.i2p)? It replied %d bytes, but nothing that looked like http/html", n)
} else {
fmt.Println("Read HTTP/HTML from idk.i2p")
log.Println("Read HTTP/HTML from idk.i2p")
}
return
// Output:
//Sending HTTP GET /
//Read HTTP/HTML from idk.i2p
}
func ExampleStreamListener() {
// One server Accept()ing on a StreamListener, and one client that Dials
// through I2P to the server. Server writes "Hello world!" through a SAMConn
// (which implements net.Conn) and the client prints the message.
const samBridge = "127.0.0.1:7656"
sam, err := NewSAM(samBridge)
if err != nil {
fmt.Println(err.Error())
return
}
defer sam.Close()
keys, err := sam.NewKeys()
if err != nil {
fmt.Println(err.Error())
return
}
quit := make(chan bool)
// Client connecting to the server
go func(server i2pkeys.I2PAddr) {
csam, err := NewSAM(samBridge)
if err != nil {
fmt.Println(err.Error())
return
}
defer csam.Close()
keys, err := csam.NewKeys()
if err != nil {
fmt.Println(err.Error())
return
}
cs, err := csam.NewStreamSession("client_example", keys, Options_Small)
if err != nil {
fmt.Println(err.Error())
quit <- false
return
}
conn, err := cs.DialI2P(server)
if err != nil {
fmt.Println(err.Error())
quit <- false
return
}
buf := make([]byte, 256)
n, err := conn.Read(buf)
if err != nil {
fmt.Println(err.Error())
quit <- false
return
}
fmt.Println(string(buf[:n]))
quit <- true
}(keys.Addr()) // end of client
ss, err := sam.NewStreamSession("server_example", keys, Options_Small)
if err != nil {
fmt.Println(err.Error())
return
}
l, err := ss.Listen()
if err != nil {
fmt.Println(err.Error())
return
}
conn, err := l.Accept()
if err != nil {
fmt.Println(err.Error())
return
}
conn.Write([]byte("Hello world!"))
<-quit // waits for client to die, for example only
// Output:
//Hello world!
}