initial commit
This commit is contained in:
13
LICENSE.txt
Normal file
13
LICENSE.txt
Normal file
@ -0,0 +1,13 @@
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
6
README.txt
Normal file
6
README.txt
Normal file
@ -0,0 +1,6 @@
|
||||
yagsl
|
||||
=====
|
||||
|
||||
This is experimental and expect lots of breaking changes.
|
||||
|
||||
Do not use this in production or at all.
|
74
sambridge/naming.go
Normal file
74
sambridge/naming.go
Normal file
@ -0,0 +1,74 @@
|
||||
package sambridge
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
resultOk string = "OK"
|
||||
invalidKey string = "INVALID_KEY"
|
||||
keyNotFound string = "KEY_NOT_FOUND"
|
||||
)
|
||||
|
||||
var (
|
||||
errKeyNotFound = errors.New("Key not found")
|
||||
errUnknownNamingLookup = errors.New("Unknown naming lookup error")
|
||||
)
|
||||
|
||||
type reply struct {
|
||||
name string
|
||||
value string
|
||||
err error
|
||||
}
|
||||
|
||||
type namingHandler struct {
|
||||
incoming chan reply
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
func (m *SAMBridge) Lookup(name string) (string, error) {
|
||||
var lookupReply reply
|
||||
m.newNamingHandler()
|
||||
|
||||
go m.Send(fmt.Sprintf("NAMING LOOKUP NAME=%s\n", name))
|
||||
for reply := range m.namingHandler.incoming {
|
||||
lookupReply = reply
|
||||
break
|
||||
}
|
||||
if lookupReply.err != nil {
|
||||
return name, lookupReply.err
|
||||
}
|
||||
return lookupReply.value, nil
|
||||
}
|
||||
|
||||
func (m *SAMBridge) namingReply(line string) {
|
||||
reply := reply{}
|
||||
|
||||
fields := strings.Fields(line)
|
||||
m.namingHandler.m.Lock()
|
||||
reply.name = strings.SplitN(fields[3], "=", 2)[1]
|
||||
switch strings.SplitN(fields[2], "=", 2)[1] { // result
|
||||
case resultOk:
|
||||
reply.err = nil
|
||||
reply.value = strings.SplitN(fields[4], "=", 2)[1]
|
||||
case invalidKey:
|
||||
reply.err = errInvalidKey
|
||||
case keyNotFound:
|
||||
reply.err = errKeyNotFound
|
||||
default:
|
||||
reply.err = errUnknownNamingLookup
|
||||
}
|
||||
m.namingHandler.incoming <- reply
|
||||
m.namingHandler.m.Unlock()
|
||||
}
|
||||
|
||||
func (m *SAMBridge) newNamingHandler() {
|
||||
if m.namingHandler == nil {
|
||||
t := new(namingHandler)
|
||||
t.incoming = make(chan reply)
|
||||
m.namingHandler = t
|
||||
}
|
||||
}
|
96
sambridge/sambridge.go
Normal file
96
sambridge/sambridge.go
Normal file
@ -0,0 +1,96 @@
|
||||
package sambridge
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SAMBridge struct {
|
||||
Options SamOptions
|
||||
Conn net.Conn
|
||||
namingHandler *namingHandler
|
||||
destHandler *destHandler
|
||||
|
||||
SessionReply chan SessionReply
|
||||
StreamReply chan StreamReply
|
||||
}
|
||||
|
||||
type SamOptions struct {
|
||||
Address string
|
||||
User string
|
||||
Password string
|
||||
}
|
||||
|
||||
const handshakeReply string = "HELLO VERSION MIN=3.0 MAX=3.3\n"
|
||||
|
||||
func (s *SAMBridge) Start() {
|
||||
s.Options.Address = "127.0.0.1:7656"
|
||||
err := s.dialSAMBridge()
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
r := bufio.NewReader(s.Conn)
|
||||
for {
|
||||
line, _ := r.ReadString('\n')
|
||||
if line != "" {
|
||||
log.Printf("DEBUG line: %s", line)
|
||||
}
|
||||
if strings.Contains(line, " ") {
|
||||
first := strings.Fields(line)[0]
|
||||
switch first {
|
||||
case "HELLO":
|
||||
go s.handshakeReply(line)
|
||||
case "SESSION":
|
||||
s.sessionReply(line)
|
||||
case "STREAM":
|
||||
// TODO: Wait for an "OK" STREAM result and as soon as that happens break out of the loop so we can panic on unknown sam lines
|
||||
s.streamReply(line)
|
||||
case "DEST":
|
||||
s.destReply(line)
|
||||
case "NAMING":
|
||||
s.namingReply(line)
|
||||
case "PING":
|
||||
go s.pingReply(line)
|
||||
default:
|
||||
//panic("Unknown SAM line encountered")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SAMBridge) dialSAMBridge() error {
|
||||
c, err := net.Dial("tcp", s.Options.Address)
|
||||
s.Conn = c
|
||||
s.sendHandshake()
|
||||
return err
|
||||
}
|
||||
|
||||
func (s SAMBridge) Send(line string) error {
|
||||
if s.Conn != nil {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(line)
|
||||
if !strings.HasSuffix(line, "\n") {
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
w := bufio.NewWriter(s.Conn)
|
||||
_, err := w.WriteString(sb.String())
|
||||
|
||||
err = w.Flush()
|
||||
return err
|
||||
}
|
||||
return errors.New("Conn was closed")
|
||||
}
|
||||
|
||||
func (s SAMBridge) sendHandshake() error {
|
||||
return s.Send(handshakeReply)
|
||||
}
|
||||
|
||||
func (s SAMBridge) handshakeReply(reply string) error {
|
||||
if !strings.HasPrefix(reply, "HELLO REPLY RESULT=OK") {
|
||||
return errHandshakeFailed
|
||||
}
|
||||
return nil
|
||||
}
|
57
sambridge/sessionreply.go
Normal file
57
sambridge/sessionreply.go
Normal file
@ -0,0 +1,57 @@
|
||||
package sambridge
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SessionReply struct {
|
||||
Destination string
|
||||
Err error
|
||||
ID string
|
||||
Message string
|
||||
}
|
||||
|
||||
const (
|
||||
duplicatedDest = "DUPLICATED_DEST"
|
||||
duplicatedID = "DUPLICATED_ID"
|
||||
)
|
||||
|
||||
var (
|
||||
errHandshakeFailed = errors.New("Handshake failed")
|
||||
errInvalidKey = errors.New("invalid key error")
|
||||
errDuplicatedDest = errors.New("the destination is not a valid private destination key")
|
||||
errDuplicatedID = errors.New("the destination is already in use")
|
||||
|
||||
errUnknownSession = errors.New("Unknown session error")
|
||||
)
|
||||
|
||||
func (s SAMBridge) sessionReply(line string) {
|
||||
reply := SessionReply{}
|
||||
fields := strings.Fields(line)
|
||||
|
||||
switch strings.SplitN(fields[2], "=", 2)[1] { // result
|
||||
case resultOk:
|
||||
reply.Err = nil
|
||||
if strings.HasPrefix(fields[3], "DESTINATION") {
|
||||
reply.Destination = strings.SplitN(fields[3], "=", 2)[1]
|
||||
}
|
||||
if strings.HasPrefix(fields[3], "ID") {
|
||||
reply.ID = strings.SplitN(fields[3], "=", 2)[1]
|
||||
}
|
||||
if len(fields) > 4 {
|
||||
if strings.HasPrefix(fields[4], "MESSAGE") {
|
||||
reply.Message = strings.SplitN(fields[4], "=", 2)[1]
|
||||
}
|
||||
}
|
||||
case duplicatedID:
|
||||
reply.Err = errDuplicatedID
|
||||
case duplicatedDest:
|
||||
reply.Err = errDuplicatedDest
|
||||
case invalidKey:
|
||||
reply.Err = errInvalidKey
|
||||
default:
|
||||
reply.Err = errUnknownSession
|
||||
}
|
||||
s.SessionReply <- reply
|
||||
}
|
58
sambridge/streamreply.go
Normal file
58
sambridge/streamreply.go
Normal file
@ -0,0 +1,58 @@
|
||||
package sambridge
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type StreamReply struct {
|
||||
Err error
|
||||
Message string
|
||||
}
|
||||
|
||||
const (
|
||||
invalidID = "INVALID_ID"
|
||||
i2pErr = "I2P_ERROR"
|
||||
cantReachPeer = "CANT_REACH_PEER"
|
||||
timeout = "TIMEOUT"
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidID = errors.New("invalid ID")
|
||||
errI2PErr = errors.New("generic I2P error")
|
||||
errCantReachPeer = errors.New("can't reach peer")
|
||||
errTimeout = errors.New("timeout")
|
||||
errUnknownStream = errors.New("unknown stream error")
|
||||
)
|
||||
|
||||
func (s SAMBridge) streamReply(line string) {
|
||||
log.Printf("DEBUG STREAMREPLY: %s", line)
|
||||
reply := StreamReply{}
|
||||
fields := strings.Fields(line)
|
||||
|
||||
switch strings.SplitN(fields[2], "=", 2)[1] { // result
|
||||
case resultOk:
|
||||
reply.Err = nil
|
||||
case invalidID:
|
||||
reply.Err = errInvalidID
|
||||
case i2pErr:
|
||||
reply.Err = errI2PErr
|
||||
case cantReachPeer:
|
||||
reply.Err = errCantReachPeer
|
||||
case invalidKey:
|
||||
reply.Err = errInvalidKey
|
||||
case timeout:
|
||||
reply.Err = errTimeout
|
||||
default:
|
||||
reply.Err = errUnknownStream
|
||||
}
|
||||
|
||||
if len(fields) > 3 {
|
||||
if strings.HasPrefix(fields[3], "MESSAGE") {
|
||||
reply.Message = strings.SplitN(fields[3], "=", 2)[1]
|
||||
}
|
||||
}
|
||||
|
||||
s.StreamReply <- reply
|
||||
}
|
65
sambridge/utilitycommands.go
Normal file
65
sambridge/utilitycommands.go
Normal file
@ -0,0 +1,65 @@
|
||||
package sambridge
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type DestReply struct {
|
||||
Pub string
|
||||
Priv string
|
||||
err error
|
||||
}
|
||||
|
||||
type destHandler struct {
|
||||
incoming chan DestReply
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
func (m SAMBridge) DestGenerate(signatureType string) (DestReply, error) {
|
||||
var destReply DestReply
|
||||
m.newDestHandler()
|
||||
|
||||
if signatureType == "" {
|
||||
signatureType = "DSA_SHA1"
|
||||
}
|
||||
m.Send(fmt.Sprintf("DEST GENERATE SIGNATURE_TYPE=%s", signatureType))
|
||||
for reply := range m.destHandler.incoming {
|
||||
destReply = reply
|
||||
break
|
||||
}
|
||||
if destReply.err != nil {
|
||||
return destReply, destReply.err
|
||||
}
|
||||
return destReply, nil
|
||||
}
|
||||
|
||||
func (m SAMBridge) destReply(reply string) {
|
||||
dest := DestReply{}
|
||||
fields := strings.Fields(reply)
|
||||
m.destHandler.m.Lock()
|
||||
if fields[2] != "RESULT=OK" {
|
||||
dest.err = errors.New(strings.TrimRight(strings.TrimLeft(strings.Join(fields[3:], " "), "MESSAGE=\""), "\""))
|
||||
} else {
|
||||
dest.Pub = strings.TrimPrefix(fields[2], "PUB=")
|
||||
dest.Priv = strings.TrimPrefix(fields[3], "PRIV=")
|
||||
dest.err = nil
|
||||
}
|
||||
m.destHandler.incoming <- dest
|
||||
m.destHandler.m.Unlock()
|
||||
}
|
||||
|
||||
func (s SAMBridge) pingReply(reply string) error {
|
||||
s.Send(fmt.Sprintf("PONG %s", strings.TrimLeft(reply, "PING ")))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *SAMBridge) newDestHandler() {
|
||||
if m.destHandler == nil {
|
||||
dh := destHandler{}
|
||||
dh.incoming = make(chan DestReply)
|
||||
m.destHandler = &dh
|
||||
}
|
||||
}
|
104
session/master.go
Normal file
104
session/master.go
Normal file
@ -0,0 +1,104 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"yagsl/sambridge"
|
||||
)
|
||||
|
||||
const masterSessionStyle string = "MASTER"
|
||||
|
||||
var errMasterSessionFailed = errors.New("Failed to start MasterSession")
|
||||
|
||||
type MasterSession struct {
|
||||
Style string
|
||||
ID string
|
||||
Destination string
|
||||
Transient bool
|
||||
|
||||
Sam *sambridge.SAMBridge
|
||||
*Session
|
||||
|
||||
Ready chan bool
|
||||
|
||||
Subsessions []Subsession
|
||||
|
||||
// i2cp and streaming options go here
|
||||
}
|
||||
|
||||
func NewMasterSession(destination string) (MasterSession, error) {
|
||||
sb := new(sambridge.SAMBridge)
|
||||
sb.Options.Address = "127.0.0.1:7656"
|
||||
go sb.Start()
|
||||
ms := MasterSession{Style: "MASTER"}
|
||||
ms.Ready = make(chan bool, 1)
|
||||
if destination == "" {
|
||||
destination = "TRANSIENT"
|
||||
ms.Transient = true
|
||||
}
|
||||
ms.Sam = sb
|
||||
ms.Sam.SessionReply = make(chan sambridge.SessionReply)
|
||||
go ms.newSessionHandler()
|
||||
|
||||
ms.Sam.StreamReply = make(chan sambridge.StreamReply)
|
||||
go ms.newStreamHandler()
|
||||
|
||||
ms.ID = "master" + strconv.FormatInt(time.Now().UTC().Unix(), 10)
|
||||
ms.Destination = destination
|
||||
|
||||
time.Sleep(time.Second * 1)
|
||||
ms.Sam.Send(fmt.Sprintf("SESSION CREATE STYLE=MASTER ID=%s DESTINATION=%s\n", ms.ID, ms.Destination))
|
||||
for ready := range ms.Ready {
|
||||
if ready {
|
||||
return ms, nil
|
||||
}
|
||||
return ms, errMasterSessionFailed
|
||||
}
|
||||
return ms, nil
|
||||
}
|
||||
|
||||
func (ms *MasterSession) NewSubsession(fromPort string, toPort string, listenPort string) Subsession {
|
||||
ss := Subsession{}
|
||||
|
||||
ss.ID = "sub" + strconv.FormatInt(time.Now().UTC().Unix(), 10)
|
||||
if listenPort != "" {
|
||||
ms.Sam.Send(fmt.Sprintf("SESSION ADD STYLE=STREAM ID=%s FROM_PORT=%s LISTEN_PORT=%s SILENT=FALSE\n", ss.ID, fromPort, listenPort))
|
||||
} else {
|
||||
ms.Sam.Send(fmt.Sprintf("SESSION ADD STYLE=STREAM ID=%s FROM_PORT=%s SILENT=FALSE\n", ss.ID, fromPort))
|
||||
}
|
||||
|
||||
ms.Subsessions = append(ms.Subsessions, ss)
|
||||
|
||||
return ss
|
||||
}
|
||||
|
||||
func (ms *MasterSession) newSessionHandler() {
|
||||
var sessionReply sambridge.SessionReply
|
||||
for reply := range ms.Sam.SessionReply {
|
||||
sessionReply = reply
|
||||
break
|
||||
}
|
||||
if sessionReply.Err != nil {
|
||||
ms.Ready <- false
|
||||
}
|
||||
ms.Destination = sessionReply.Destination
|
||||
ms.Ready <- true
|
||||
}
|
||||
|
||||
func (ms *MasterSession) newStreamHandler() {
|
||||
var streamReply sambridge.StreamReply
|
||||
for reply := range ms.Sam.StreamReply {
|
||||
streamReply = reply
|
||||
break
|
||||
}
|
||||
if streamReply.Err != nil {
|
||||
log.Printf("StreamReply err: %s", streamReply.Err.Error())
|
||||
} else {
|
||||
log.Printf("StreamReply success!")
|
||||
|
||||
}
|
||||
}
|
51
session/session.go
Normal file
51
session/session.go
Normal file
@ -0,0 +1,51 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
Style string
|
||||
ID string
|
||||
Destination string
|
||||
Options SessionOptions
|
||||
}
|
||||
|
||||
type SessionOptions struct {
|
||||
Port string
|
||||
Host string
|
||||
FromPort string
|
||||
ToPort string
|
||||
ListenPort string
|
||||
ListenProtcol string
|
||||
Header bool
|
||||
// i2cp and streaming options go here
|
||||
}
|
||||
|
||||
type sessionHandler struct {
|
||||
incoming chan reply
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
type reply struct {
|
||||
full string
|
||||
|
||||
name string
|
||||
value string
|
||||
err error
|
||||
}
|
||||
|
||||
func (m *Session) SessionCreate() {
|
||||
}
|
||||
|
||||
func (s Session) sessionReply(reply string) error {
|
||||
fields := strings.Fields(reply)
|
||||
if strings.HasPrefix(fields[1], "RESULT=") {
|
||||
log.Printf("Session status result: %s", fields[1:])
|
||||
} else {
|
||||
log.Printf("Got SESSION reply: %s", reply)
|
||||
}
|
||||
return nil
|
||||
}
|
77
session/subsession.go
Normal file
77
session/subsession.go
Normal file
@ -0,0 +1,77 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
"yagsl/sambridge"
|
||||
)
|
||||
|
||||
type Subsession struct {
|
||||
*Session
|
||||
ID string
|
||||
Style string
|
||||
Sam *sambridge.SAMBridge
|
||||
Ready chan (bool)
|
||||
}
|
||||
|
||||
var (
|
||||
errSubSessionFailed = errors.New("the subsession failed :(")
|
||||
)
|
||||
|
||||
func NewStreamConnect(ID string, destination string, fromPort string) (Subsession, error) {
|
||||
sb := new(sambridge.SAMBridge)
|
||||
go sb.Start()
|
||||
ss := Subsession{
|
||||
Style: "STREAM",
|
||||
ID: ID,
|
||||
}
|
||||
ss.Sam = sb
|
||||
ss.Sam.SessionReply = make(chan sambridge.SessionReply)
|
||||
go ss.newSessionHandler()
|
||||
// TODO: Replace this handshake pause with a Ready callback to prevent a data race
|
||||
time.Sleep(time.Second * 1)
|
||||
// TODO: Refactor this sam.Send into a stream package w/a streamHandler and it's own connect message in that section
|
||||
err := ss.Sam.Send(fmt.Sprintf("STREAM CONNECT ID=%s DESTINATION=%s FROM_PORT=%s TO_PORT=0 SILENT=FALSE\n", ID, destination, fromPort))
|
||||
return ss, err
|
||||
}
|
||||
|
||||
func NewStreamAccept(ID string) (Subsession, error) {
|
||||
sb := new(sambridge.SAMBridge)
|
||||
go sb.Start()
|
||||
ss := Subsession{
|
||||
Style: "STREAM",
|
||||
ID: ID,
|
||||
}
|
||||
ss.Sam = sb
|
||||
ss.Sam.SessionReply = make(chan sambridge.SessionReply)
|
||||
go ss.newSessionHandler()
|
||||
// TODO: Replace this handshake pause with a Ready callback to prevent a data race
|
||||
time.Sleep(time.Second * 1)
|
||||
// TODO: Refactor this sam.Send into a stream package w/a streamHandler and it's own connect message in that section
|
||||
err := ss.Sam.Send(fmt.Sprintf("STREAM ACCEPT ID=%s SILENT=FALSE\n", ID))
|
||||
return ss, err
|
||||
}
|
||||
|
||||
func (ss *Subsession) newSessionHandler() {
|
||||
var sessionReply sambridge.SessionReply
|
||||
for reply := range ss.Sam.SessionReply {
|
||||
sessionReply = reply
|
||||
break
|
||||
}
|
||||
if sessionReply.Err != nil {
|
||||
ss.Ready <- false
|
||||
}
|
||||
ss.Destination = sessionReply.Destination
|
||||
ss.Ready <- true
|
||||
}
|
||||
|
||||
func (ss *Subsession) Stop() error {
|
||||
ss.Sam.Send(fmt.Sprintf("SESSION REMOVE ID=%s", ss.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Subsession) Dial(network, addr string) (net.Conn, error) {
|
||||
return s.Sam.Conn, nil
|
||||
}
|
Reference in New Issue
Block a user