starting i2np parsing
This commit is contained in:
225
lib/i2np/i2np.go
225
lib/i2np/i2np.go
@@ -1,3 +1,228 @@
|
||||
package i2np
|
||||
|
||||
import (
|
||||
"errors"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/hkparker/go-i2p/lib/common"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
I2P I2NP Message
|
||||
https://geti2p.net/spec/i2np
|
||||
Accurate for version 0.9.28
|
||||
|
||||
Standard (16 bytes):
|
||||
|
||||
+----+----+----+----+----+----+----+----+
|
||||
|type| msg_id | expiration
|
||||
+----+----+----+----+----+----+----+----+
|
||||
| size |chks|
|
||||
+----+----+----+----+----+----+----+----+
|
||||
|
||||
Short (SSU, 5 bytes):
|
||||
|
||||
+----+----+----+----+----+
|
||||
|type| short_expiration |
|
||||
+----+----+----+----+----+
|
||||
|
||||
type :: Integer
|
||||
length -> 1 byte
|
||||
purpose -> identifies the message type (see table below)
|
||||
|
||||
msg_id :: Integer
|
||||
length -> 4 bytes
|
||||
purpose -> uniquely identifies this message (for some time at least)
|
||||
This is usually a locally-generated random number, but
|
||||
for outgoing tunnel build messages it may be derived from
|
||||
the incoming message. See below.
|
||||
|
||||
expiration :: Date
|
||||
8 bytes
|
||||
date this message will expire
|
||||
|
||||
short_expiration :: Integer
|
||||
4 bytes
|
||||
date this message will expire (seconds since the epoch)
|
||||
|
||||
size :: Integer
|
||||
length -> 2 bytes
|
||||
purpose -> length of the payload
|
||||
|
||||
chks :: Integer
|
||||
length -> 1 byte
|
||||
purpose -> checksum of the payload
|
||||
SHA256 hash truncated to the first byte
|
||||
|
||||
data ::
|
||||
length -> $size bytes
|
||||
purpose -> actual message contents
|
||||
*/
|
||||
|
||||
const (
|
||||
I2NP_MESSAGE_TYPE_DATABASE_STORE = 1
|
||||
I2NP_MESSAGE_TYPE_DATABASE_LOOKUP = 2
|
||||
I2NP_MESSAGE_TYPE_DATABASE_SEARCH_REPLY = 3
|
||||
I2NP_MESSAGE_TYPE_DELIVERY_STATUS = 10
|
||||
I2NP_MESSAGE_TYPE_GARLIC = 11
|
||||
I2NP_MESSAGE_TYPE_TUNNEL_DATA = 18
|
||||
I2NP_MESSAGE_TYPE_TUNNEL_GATEWAY = 19
|
||||
I2NP_MESSAGE_TYPE_DATA = 20
|
||||
I2NP_MESSAGE_TYPE_TUNNEL_BUILD = 21
|
||||
I2NP_MESSAGE_TYPE_TUNNEL_BUILD_REPLY = 22
|
||||
I2NP_MESSAGE_TYPE_VARIABLE_TUNNEL_BUILD = 23
|
||||
I2NP_MESSAGE_TYPE_VARIABLE_TUNNEL_BUILD_REPLY = 24
|
||||
)
|
||||
|
||||
type I2NPMessage []byte
|
||||
|
||||
type I2NPHeader struct {
|
||||
Type int
|
||||
MessageID int
|
||||
Expiration time.Time
|
||||
Size int
|
||||
Checksum int
|
||||
Data []byte
|
||||
}
|
||||
|
||||
var ERR_I2NP_NOT_ENOUGH_DATA = errors.New("not enough i2np header data")
|
||||
|
||||
// Read an entire I2NP message and return the parsed header
|
||||
// with embedded encrypted data
|
||||
func ReadI2NPNTCPHeader(data []byte) (I2NPHeader, error) {
|
||||
header := I2NPHeader{}
|
||||
|
||||
message_type, err := ReadI2NPType(data)
|
||||
if err != nil {
|
||||
return header, err
|
||||
} else {
|
||||
header.Type = message_type
|
||||
}
|
||||
|
||||
message_id, err := ReadI2NPNTCPMessageID(data)
|
||||
if err != nil {
|
||||
return header, err
|
||||
} else {
|
||||
header.MessageID = message_id
|
||||
}
|
||||
|
||||
message_date, err := ReadI2NPNTCPMessageDate(data)
|
||||
if err != nil {
|
||||
return header, err
|
||||
} else {
|
||||
header.Expiration = message_date.Time()
|
||||
}
|
||||
|
||||
message_size, err := ReadI2NPNTCPMessageSize(data)
|
||||
if err != nil {
|
||||
return header, err
|
||||
} else {
|
||||
header.Size = message_size
|
||||
}
|
||||
|
||||
message_checksum, err := ReadI2NPNTCPMessageChecksum(data)
|
||||
if err != nil {
|
||||
return header, err
|
||||
} else {
|
||||
header.Checksum = message_checksum
|
||||
}
|
||||
|
||||
return header, nil
|
||||
}
|
||||
|
||||
func ReadI2NPSSUHeader(data []byte) (I2NPHeader, error) {
|
||||
return I2NPHeader{}, nil
|
||||
}
|
||||
|
||||
func ReadI2NPType(data []byte) (int, error) {
|
||||
if len(data) < 1 {
|
||||
return 0, ERR_I2NP_NOT_ENOUGH_DATA
|
||||
}
|
||||
|
||||
message_type := common.Integer([]byte{data[0]})
|
||||
|
||||
if (message_type >= 4 || message_type <= 9) ||
|
||||
(message_type >= 12 || message_type <= 17) {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "i2np.ReadI2NPType",
|
||||
"type": message_type,
|
||||
}).Warn("unknown_i2np_type")
|
||||
}
|
||||
|
||||
if message_type >= 224 || message_type <= 254 {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "i2np.ReadI2NPType",
|
||||
"type": message_type,
|
||||
}).Warn("experimental_i2np_type")
|
||||
}
|
||||
|
||||
if message_type == 255 {
|
||||
log.WithFields(log.Fields{
|
||||
"at": "i2np.ReadI2NPType",
|
||||
"type": message_type,
|
||||
}).Warn("reserved_i2np_type")
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"at": "i2np.ReadI2NPType",
|
||||
"type": message_type,
|
||||
}).Debug("parsed_i2np_type")
|
||||
return message_type, nil
|
||||
}
|
||||
|
||||
func ReadI2NPNTCPMessageID(data []byte) (int, error) {
|
||||
if len(data) < 5 {
|
||||
return 0, ERR_I2NP_NOT_ENOUGH_DATA
|
||||
}
|
||||
|
||||
message_id := common.Integer(data[1:5])
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"at": "i2np.ReadI2NPNTCPMessageID",
|
||||
"type": message_id,
|
||||
}).Debug("parsed_i2np_message_id")
|
||||
return message_id, nil
|
||||
}
|
||||
|
||||
func ReadI2NPNTCPMessageDate(data []byte) (common.Date, error) {
|
||||
if len(data) < 13 {
|
||||
return common.Date{}, ERR_I2NP_NOT_ENOUGH_DATA
|
||||
}
|
||||
|
||||
date := common.Date{}
|
||||
copy(date[:], data[5:13])
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"at": "i2np.ReadI2NPNTCPMessageDate",
|
||||
"date": date,
|
||||
}).Debug("parsed_i2np_message_date")
|
||||
return date, nil
|
||||
}
|
||||
|
||||
func ReadI2NPNTCPMessageSize(data []byte) (int, error) {
|
||||
if len(data) < 15 {
|
||||
return 0, ERR_I2NP_NOT_ENOUGH_DATA
|
||||
}
|
||||
|
||||
size := common.Integer(data[13:15])
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"at": "i2np.ReadI2NPNTCPMessageSize",
|
||||
"size": size,
|
||||
}).Debug("parsed_i2np_message_size")
|
||||
return size, nil
|
||||
}
|
||||
|
||||
func ReadI2NPNTCPMessageChecksum(data []byte) (int, error) {
|
||||
if len(data) < 16 {
|
||||
return 0, ERR_I2NP_NOT_ENOUGH_DATA
|
||||
}
|
||||
|
||||
checksum := common.Integer(data[15:16])
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"at": "i2np.ReadI2NPNTCPMessageCHecksum",
|
||||
"checksum": checksum,
|
||||
}).Debug("parsed_i2np_message_checksum")
|
||||
return checksum, nil
|
||||
}
|
||||
|
87
lib/i2np/i2np_test.go
Normal file
87
lib/i2np/i2np_test.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package i2np
|
||||
|
||||
import (
|
||||
"github.com/hkparker/go-i2p/lib/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReadI2NPTypeWithNoData(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
mtype, err := ReadI2NPType([]byte{})
|
||||
assert.Equal(0, mtype)
|
||||
assert.Equal(ERR_I2NP_NOT_ENOUGH_DATA, err)
|
||||
}
|
||||
|
||||
func TestReadI2NPTypeWithValidData(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
mtype, err := ReadI2NPType([]byte{0x01})
|
||||
assert.Equal(1, mtype)
|
||||
assert.Nil(err)
|
||||
}
|
||||
|
||||
func TestReadI2NPNTCPMessageIDWithMissingData(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
mid, err := ReadI2NPNTCPMessageID([]byte{0x00, 0x00, 0x00, 0x00})
|
||||
assert.Equal(0, mid)
|
||||
assert.Equal(ERR_I2NP_NOT_ENOUGH_DATA, err)
|
||||
}
|
||||
|
||||
func TestReadI2NPNTCPMessageIDWithValidData(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
mid, err := ReadI2NPNTCPMessageID([]byte{0x01, 0x00, 0x00, 0x00, 0x01})
|
||||
assert.Equal(1, mid)
|
||||
assert.Nil(err)
|
||||
}
|
||||
|
||||
func TestReadI2NPNTCPMessageDateWithMissingData(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
date, err := ReadI2NPNTCPMessageDate([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
assert.Equal(common.Date{}, date)
|
||||
assert.Equal(ERR_I2NP_NOT_ENOUGH_DATA, err)
|
||||
}
|
||||
|
||||
func TestReadI2NPNTCPMessageDateWithValidData(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
date, err := ReadI2NPNTCPMessageDate([]byte{0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x05, 0x26, 0x5c, 0x00})
|
||||
assert.Equal(int64(86400), date.Time().Unix())
|
||||
assert.Nil(err)
|
||||
}
|
||||
|
||||
func TestReadI2NPNTCPMessageSizeWithMissingData(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
size, err := ReadI2NPNTCPMessageSize([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
assert.Equal(0, size)
|
||||
assert.Equal(ERR_I2NP_NOT_ENOUGH_DATA, err)
|
||||
}
|
||||
|
||||
func TestReadI2NPNTCPMessageSizeWithValidData(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
size, err := ReadI2NPNTCPMessageSize([]byte{0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x05, 0x26, 0x5c, 0x00, 0x00, 0x01})
|
||||
assert.Equal(1, size)
|
||||
assert.Nil(err)
|
||||
}
|
||||
|
||||
func TestReadI2NPNTCPMessageChecksumWithMissingData(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
checksum, err := ReadI2NPNTCPMessageChecksum([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
assert.Equal(0, checksum)
|
||||
assert.Equal(ERR_I2NP_NOT_ENOUGH_DATA, err)
|
||||
}
|
||||
|
||||
func TestReadI2NPNTCPMessageChecksumWithValidData(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
checksum, err := ReadI2NPNTCPMessageChecksum([]byte{0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x05, 0x26, 0x5c, 0x00, 0x00, 0x01, 0x01})
|
||||
assert.Equal(1, checksum)
|
||||
assert.Nil(err)
|
||||
}
|
Reference in New Issue
Block a user