diff --git a/client.go b/client.go index 9027423..ae0b54e 100644 --- a/client.go +++ b/client.go @@ -1,31 +1,36 @@ package go_i2cp - +import ( + sll "github.com/emirpasic/gods/lists/singlylinkedlist" + "net/rpc" + "bytes" + "sync" +) const I2P_CLIENT_VERSION string = "0.9.11" -const TAG string = "CLIENT" -const I2CP_PROTOCOL_INIT int = 0x2a +const TAG = CLIENT +const I2CP_PROTOCOL_INIT uint8 = 0x2a const I2CP_MESSAGE_SIZE int = 0xffff const I2CP_MAX_SESSIONS int = 0xffff const I2CP_MAX_SESSIONS_PER_CLIENT int = 32 const I2CP_MSG_ANY int = 0 -const I2CP_MSG_BANDWIDTH_LIMITS int = 23 -const I2CP_MSG_CREATE_LEASE_SET int = 4 -const I2CP_MSG_CREATE_SESSION int = 1 -const I2CP_MSG_DEST_LOOKUP int = 34 -const I2CP_MSG_DEST_REPLY int = 35 -const I2CP_MSG_DESTROY_SESSION int = 3 -const I2CP_MSG_DISCONNECT int = 30 -const I2CP_MSG_GET_BANDWIDTH_LIMITS int = 8 -const I2CP_MSG_GET_DATE int = 32 -const I2CP_MSG_HOST_LOOKUP int = 38 -const I2CP_MSG_HOST_REPLY int = 39 -const I2CP_MSG_MESSAGE_STATUS int = 22 -const I2CP_MSG_PAYLOAD_MESSAGE int = 31 -const I2CP_MSG_REQUEST_LEASESET int = 21 -const I2CP_MSG_REQUEST_VARIABLE_LEASESET int = 37 -const I2CP_MSG_SEND_MESSAGE int = 5 -const I2CP_MSG_SESSION_STATUS int = 20 -const I2CP_MSG_SET_DATE int = 33 +const I2CP_MSG_BANDWIDTH_LIMITS uint8 = 23 +const I2CP_MSG_CREATE_LEASE_SET uint8 = 4 +const I2CP_MSG_CREATE_SESSION uint8 = 1 +const I2CP_MSG_DEST_LOOKUP uint8 = 34 +const I2CP_MSG_DEST_REPLY uint8 = 35 +const I2CP_MSG_DESTROY_SESSION uint8 = 3 +const I2CP_MSG_DISCONNECT uint8 = 30 +const I2CP_MSG_GET_BANDWIDTH_LIMITS uint8 = 8 +const I2CP_MSG_GET_DATE uint8 = 32 +const I2CP_MSG_HOST_LOOKUP uint8 = 38 +const I2CP_MSG_HOST_REPLY uint8 = 39 +const I2CP_MSG_MESSAGE_STATUS uint8 = 22 +const I2CP_MSG_PAYLOAD_MESSAGE uint8 = 31 +const I2CP_MSG_REQUEST_LEASESET uint8 = 21 +const I2CP_MSG_REQUEST_VARIABLE_LEASESET uint8 = 37 +const I2CP_MSG_SEND_MESSAGE uint8 = 5 +const I2CP_MSG_SESSION_STATUS uint8 = 20 +const I2CP_MSG_SET_DATE uint8 = 33 /* Router capabilities */ const ROUTER_CAN_HOST_LOOKUP int = 1 @@ -50,8 +55,184 @@ const ( type ClientCallBacks struct { opaque *interface{} - onDisconnect *func(*Client, string, *interface{}) + onDisconnect func(*Client, string, *interface{}) onLog func(*Client, LoggerTags, string) } -type Client struct { +type LookupEntry struct { + address string + session Session } +type RouterInfo struct { + date uint64 + version string + capabilities uint32 +} + +type Client struct { + logger *LoggerCallbacks // TODO idk wat this is for + callbacks ClientCallBacks + properties [NR_OF_I2CP_CLIENT_PROPERTIES]string + tcp *Tcp + outputStream *Stream + messageStream *Stream + router RouterInfo + outputQueue *sll.List + sessions []*Session + n_sessions int + lookup map[string]LookupEntry + lookupReq map[int]string + lock sync.Mutex +} + +// NewClient creates a new i2p client with the specified callbacks +func NewClient(callbacks ClientCallBacks) (c *Client) { + c = new(Client) + c.callbacks = callbacks + LogInit(nil, ERROR) + c.outputStream = bytes.NewBuffer(make([]byte, 0, I2CP_MESSAGE_SIZE)) + c.messageStream = bytes.NewBuffer(make([]byte, 0, I2CP_MESSAGE_SIZE)) + c.setDefaultProperties() + c.lookup = make(map[string]LookupEntry, 1000) + c.lookupReq = make(map[int]string, 1000) + c.outputQueue = sll.New() + return +} + +func (c *Client) setDefaultProperties() { + c.properties[CLIENT_PROP_ROUTER_ADDRESS] = "127.0.0.1" + c.properties[CLIENT_PROP_ROUTER_PORT] = "7654" + c.properties[CLIENT_PROP_ROUTER_PORT] = "7654" + // TODO PARSE I2CP config file +} +func (c *Client) Connect() { + Info(0, "Client connecting to i2cp at %s:%s", c.properties[CLIENT_PROP_ROUTER_ADDRESS], c.properties[CLIENT_PROP_ROUTER_PORT]); + err := c.tcp.Connect() + if err != nil { + panic(err) + } + c.outputStream.Reset() + c.outputStream.WriteByte(I2CP_PROTOCOL_INIT) + _, err = c.tcp.Send(c.outputStream) + Debug(PROTOCOL, "Sending protocol byte message") + c.messageGetDate(false) + c.recvMessage(I2CP_MSG_SET_DATE, c.messageStream, true) +} + +// TODO Write messageGetDate +func (c *Client) messageGetDate(queue bool) { + Debug(PROTOCOL, "Sending GetDateMessage") + c.messageStream.Reset() + c.messageStream.WriteString(I2P_CLIENT_VERSION) + /* write new 0.9.10 auth mapping if username property is set */ + if c.properties[CLIENT_PROP_USERNAME] != "" { + auth := NewStream(make([]byte, 0, 512)) + auth.WriteString("i2cp.password") + auth.WriteByte('=') + auth.WriteString(c.properties[CLIENT_PROP_PASSWORD]) + auth.WriteByte(';') + auth.WriteString("i2cp.username") + auth.WriteByte('=') + auth.WriteString(c.properties[CLIENT_PROP_USERNAME]) + auth.WriteByte(';') + c.messageStream.WriteUint16(uint16(auth.Len())) + c.messageStream.Write(auth.Bytes()) + auth.Reset() + } + if err := c.sendMessage(I2CP_MSG_GET_DATE, c.messageStream, queue); + err != nil { + Error(0, "%s", "error while sending GetDateMessage."); + } +} + +func (c *Client) sendMessage(typ uint8, stream *Stream, queue bool) (err error) { + send := NewStream(make([]byte, stream.Len() + 4 + 1)) + err = send.WriteUint32(uint32(stream.Len())) + err = send.WriteByte(typ) + _, err = send.Write(stream.Bytes()) + if queue { + Debug(PROTOCOL, "Putting %d bytes message on the output queue.", send.Len()) + c.lock.Lock() + c.outputQueue.Add(send) + c.lock.Unlock() + } else { + err = c.tcp.Send(send) + } + return +} + +func (c *Client) recvMessage(typ uint8, stream *Stream, dispatch bool) (err error) { + length := uint32(0) + msgType := uint8(0) + var i int + firstFive := NewStream(make([]byte, 5)) + i, err = c.tcp.Receive(firstFive) + if i ==0 { + c.callbacks.onDisconnect(c, "Didn't receive anything", nil) + } + length, err = firstFive.ReadUint32() + msgType, err = firstFive.ReadByte() + if (typ == I2CP_MSG_SET_DATE) && (length > 0xffff) { + Fatal(PROTOCOL, "Unexpected response, your router is probably configured to use SSL") + } + if length > 0xffff { + Fatal(PROTOCOL, "unexpected message length, length > 0xffff") + } + if (typ != 0) && (msgType != typ) { + Error(PROTOCOL, "expected message type %d, received %d", typ, msgType) + } + // receive rest + stream.ChLen(int(length)) + i, err = c.tcp.Receive(stream) + + if dispatch { + c.onMessage(msgType, stream) + } + return +} +func (c *Client) onMessage(msgType uint8, stream *Stream) { + switch msgType { + case I2CP_MSG_SET_DATE: c.onMsgSetDate(stream) + case I2CP_MSG_DISCONNECT: c.onMsgDisconnect(stream) + case I2CP_MSG_PAYLOAD_MESSAGE: c.onMsgPayload(stream) + case I2CP_MSG_MESSAGE_STATUS: c.onMsgStatus(stream) + case I2CP_MSG_DEST_REPLY: c.onMsgDestReply(stream) + case I2CP_MSG_BANDWIDTH_LIMITS: c.onMsgBandwithLimit(stream) + case I2CP_MSG_SESSION_STATUS: c.onMsgSessionStatus(stream) + case I2CP_MSG_REQUEST_VARIABLE_LEASESET: c.onMsgReqVariableLease(stream) + case I2CP_MSG_HOST_REPLY: c.onMsgHostReply(stream) + default: Info(TAG, "%s", "recieved unhandled i2cp message.") + } +} +func (c *Client) onMsgSetDate(stream *Stream) { + Debug(TAG|PROTOCOL, "Received SetDate message.") + var err error + c.router.date, err = stream.ReadUint64() + c.router.version, err = stream.R + if err != nil { + Error(TAG|PROTOCOL, "Could not read SetDate correctly data") + } +} +func (c *Client) onMsgDisconnect(stream *Stream) { + +} +func (c *Client) onMsgPayload(stream *Stream) { + +} +func (c *Client) onMsgStatus(stream *Stream) { + +} +func (c *Client) onMsgDestReply(stream *Stream) { + +} +func (c *Client) onMsgBandwithLimit(stream *Stream) { + +} +func (c *Client) onMsgSessionStatus(stream *Stream) { + +} +func (c *Client) onMsgReqVariableLease(stream *Stream) { + +} +func (c *Client) onMsgHostReply(stream *Stream) { + +} \ No newline at end of file diff --git a/i2pc.go b/i2pc.go new file mode 100644 index 0000000..f8a1f45 --- /dev/null +++ b/i2pc.go @@ -0,0 +1,14 @@ +package go_i2cp + +func Init() { + tcpInit() +} + +func Deinit() { + tcpDeinit() +} + +const ( + CLIENT uint32 = (iota +1) << 8 + +) \ No newline at end of file diff --git a/logger.go b/logger.go new file mode 100644 index 0000000..b5d4bfd --- /dev/null +++ b/logger.go @@ -0,0 +1,91 @@ +package go_i2cp + +import "fmt" + +const ( + PROTOCOL = (1 << 0) + LOGIC = (1 << 1) + + DEBUG = (1 << 4) + INFO = (1 << 5) + WARNING = (1 << 6) + ERROR = (1 << 7) + FATAL = (1 << 8) + + STRINGMAP = (1 << 9) + INTMAP = (1 << 10) + QUEUE = (1 << 11) + STREAM = (1 << 12) + CRYPTO = (1 << 13) + TCP = (1 << 14) + CLIENT = (1 << 15) + CERTIFICATE = (1 << 16) + LEASE = (1 << 17) + DESTINATION = (1 << 18) + SESSION = (1 << 19) + SESSION_CONFIG = (1 << 20) + TEST = (1 << 21) + DATAGRAM = (1 << 22) + CONFIG_FILE = (1 << 23) + VERSION = (1 << 24) + + TAG_MASK = 0x0000000f + LEVEL_MASK = 0x000001f0 + COMPONENT_MASK = 0xfffffe00 + + ALL = 0xffffffff +) + +type LoggerTags = uint32 +type LoggerCallbacks struct { + opaque *interface{} + onLog func(*Logger, LoggerTags, string) +} +type Logger struct { + callbacks *LoggerCallbacks + logLevel int +} + +var logInstance *Logger + +// TODO filter +func LogInit(callbacks *LoggerCallbacks, level int) { + logInstance = &Logger{callbacks: callbacks} + logInstance.setLogLevel(level) +} +func Debug(tags LoggerTags, message string, args ...interface{}) { + logInstance.log(tags|DEBUG, message, args...) +} +func Info(tags LoggerTags, message string, args ...interface{}) { + logInstance.log(tags|INFO, message, args...) +} +func Warning(tags LoggerTags, message string, args ...interface{}) { + logInstance.log(tags|WARNING, message, args...) +} +func Error(tags LoggerTags, message string, args ...interface{}) { + logInstance.log(tags|ERROR, message, args...) +} +func Fatal(tags LoggerTags, message string, args ...interface{}) { + logInstance.log(tags|FATAL, message, args...) +} + +func (l *Logger) log(tags LoggerTags, format string, args ...interface{}) { + if l.callbacks == nil { + fmt.Printf(format, args) + } else { + l.callbacks.onLog(l, tags, fmt.Sprintf(format, args)) + } +} + +func (l *Logger) setLogLevel(level int) { + switch level { + case DEBUG: + case INFO: + case WARNING: + case ERROR: + case FATAL: + l.logLevel = level + default: + l.logLevel = ERROR + } +} diff --git a/stream.go b/stream.go new file mode 100644 index 0000000..7d7ea2c --- /dev/null +++ b/stream.go @@ -0,0 +1,116 @@ +package go_i2cp + +import ( + "fmt" + "os" + "encoding/binary" + "bytes" + "io" +) + +type Stream = bytes.Buffer +func NewStream(buf []byte) *Stream { + return bytes.NewBuffer(buf) +} +func (s *Stream) ReadUint16() (r uint16, err error) { + bts := make([]byte, 2) + _, err = s.Read(bts) + r = binary.LittleEndian.Uint16(bts) + return +} +func (s *Stream) ReadUint32() (r uint32, err error) { + bts := make([]byte, 4) + _, err = s.Read(bts) + r = binary.LittleEndian.Uint32(bts) + return +} +func (s *Stream) ReadUint64() (r uint64, err error) { + bts := make([]byte, 8) + _, err = s.Read(bts) + r = binary.LittleEndian.Uint64(bts) + return +} + +func (s *Stream) WriteUint16(i uint16) (err error) { + bts := make([]byte, 2) + binary.LittleEndian.PutUint16(bts, i) + _, err = s.Write(bts) + return +} +func (s *Stream) WriteUint32(i uint32) (err error) { + bts := make([]byte, 4) + binary.LittleEndian.PutUint32(bts, i) + _, err = s.Write(bts) + return +} +func (s *Stream) WriteUint64(i uint64) (err error) { + bts := make([]byte, 8) + binary.LittleEndian.PutUint64(bts, i) + _, err = s.Write(bts) + return +} +func (s *Stream) loadFile(f os.File) (err error) { + _, err = f.Read(s.Bytes()) + return +} + +func (s *Stream) ChLen(len int) { + byt := s.Bytes() + byt = byt[:len] +} + +/*type Stream struct { + data []byte + size uint32 + p uint32 + end uint32 +} + +func (s *Stream) Init(len uint32) { + data := make([]byte, len) + s.data = data + s.size = len + s.Reset() +} + +func (s *Stream) Reset() { + s.end = s.size - 1 + s.p = 0 +} + +func (s *Stream) Seek(a uint32) { + s.p = a +} +func (s *Stream) Advance() { + s.p += 1 +} +func (s *Stream) Tell() uint32 { return s.p } +func (s *Stream) MarkEnd() { s.end = s.p} +func (s *Stream) Eof() bool { return s.end < s.p} +func (s *Stream) Debug() {fmt.Printf("STREAM: data %p size %d p %d end %d", s.data, s.size, s.p, s.end)} +func (s *Stream) Check(len uint32) { + if (s.p + len) > s.size { + s.Debug() + // TODO better error message + os.Exit(2) + } +} +func (s *Stream) Dump(file os.File) { + file.Write(s.data) + defer file.Close() +} +func (s *Stream) Skip(n uint32) { + s.Check(n) + s.p += n +} +func (s *Stream) ReadUint8() uint8 { + s.Check(1) + defer s.Advance() + return s.data[s.p] +} +func (s *Stream) ReadUint8p(len uint32) []uint8 { + s.Check(len) + defer s.Skip(len) + return s.data[s.p:len] +} +*/ \ No newline at end of file diff --git a/tcp.go b/tcp.go new file mode 100644 index 0000000..d5c1860 --- /dev/null +++ b/tcp.go @@ -0,0 +1,35 @@ +package go_i2cp + +import "net" + +func tcpInit() { + address = net.TCPAddr{ + IP: net.ParseIP("127.0.0.1"), + Port: 7654, + } +} + +func tcpDeinit() { + +} + +var address net.TCPAddr +var conn *net.TCPConn + +func (tcp *Tcp) Connect() (err error) { + conn, err = net.DialTCP("tcp", nil, &address ) + return +} + +func (tcp *Tcp) Send(buf *Stream) (i int, err error) { + i, err = conn.Write(buf.Bytes()) + return +} + +func (tcp *Tcp) Receive(buf *Stream) (i int, err error) { + i, err = conn.Read(buf.Bytes()) +} + +type Tcp struct { + +} \ No newline at end of file