starting i2np parsing

This commit is contained in:
Hayden
2017-06-29 23:14:17 -07:00
parent 798650cfd5
commit 1dc86bdbac
2 changed files with 312 additions and 0 deletions

View File

@@ -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
View 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)
}