i2np structs

This commit is contained in:
Hayden
2017-07-02 15:53:10 -07:00
parent 1dc86bdbac
commit f3af385fc5
19 changed files with 1251 additions and 229 deletions

View File

@ -0,0 +1,163 @@
package i2np
import (
"github.com/hkparker/go-i2p/lib/common"
"github.com/hkparker/go-i2p/lib/tunnel"
)
/*
I2P I2NP BuildRequestRecord
https://geti2p.net/spec/i2np
Accurate for version 0.9.28
ElGamal and AES encrypted:
+----+----+----+----+----+----+----+----+
| encrypted data... |
~ ~
| |
+----+----+----+----+----+----+----+----+
encrypted_data :: ElGamal and AES encrypted data
length -> 528
total length: 528
ElGamal encrypted:
+----+----+----+----+----+----+----+----+
| toPeer |
+ +
| |
+----+----+----+----+----+----+----+----+
| encrypted data... |
~ ~
| |
+----+----+----+----+----+----+----+----+
toPeer :: First 16 bytes of the SHA-256 Hash of the peer's RouterIdentity
length -> 16 bytes
encrypted_data :: ElGamal-2048 encrypted data (see notes)
length -> 512
total length: 528
Cleartext:
+----+----+----+----+----+----+----+----+
| receive_tunnel | our_ident |
+----+----+----+----+ +
| |
+ +
| |
+ +
| |
+ +----+----+----+----+
| | next_tunnel |
+----+----+----+----+----+----+----+----+
| next_ident |
+ +
| |
+ +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
| layer_key |
+ +
| |
+ +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
| iv_key |
+ +
| |
+ +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
| reply_key |
+ +
| |
+ +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
| reply_iv |
+ +
| |
+----+----+----+----+----+----+----+----+
|flag| request_time | send_msg_id
+----+----+----+----+----+----+----+----+
| |
+----+ +
| 29 bytes padding |
+ +
| |
+ +----+----+
| |
+----+----+----+----+----+----+
receive_tunnel :: TunnelId
length -> 4 bytes
our_ident :: Hash
length -> 32 bytes
next_tunnel :: TunnelId
length -> 4 bytes
next_ident :: Hash
length -> 32 bytes
layer_key :: SessionKey
length -> 32 bytes
iv_key :: SessionKey
length -> 32 bytes
reply_key :: SessionKey
length -> 32 bytes
reply_iv :: data
length -> 16 bytes
flag :: Integer
length -> 1 byte
request_time :: Integer
length -> 4 bytes
Hours since the epoch, i.e. current time / 3600
send_message_id :: Integer
length -> 4 bytes
padding :: Data
length -> 29 bytes
source -> random
total length: 222
*/
type BuildRequestRecordELGamalAES [528]byte
type BuildRequestRecordELGamal [528]byte
type BuildRequestRecord struct {
ReceiveTunnel tunnel.TunnelID
OurIdent common.Hash
NextTunnel tunnel.TunnelID
NextIdent common.Hash
LayerKey common.SessionKey
IVKey common.SessionKey
ReplyKey common.SessionKey
ReplyIV [16]byte
flag int
RequestTime int
SendMessageID int
Padding [29]byte
}

View File

@ -0,0 +1,48 @@
package i2np
import (
"github.com/hkparker/go-i2p/lib/common"
)
/*
I2P I2NP BuildResponseRecord
https://geti2p.net/spec/i2np
Accurate for version 0.9.28
Encrypted:
bytes 0-527 :: AES-encrypted record (note: same size as BuildRequestRecord)
Unencrypted:
+----+----+----+----+----+----+----+----+
| |
+ +
| |
+ SHA-256 Hash of following bytes +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
| random data... |
~ ~
| |
+ +----+
| | ret|
+----+----+----+----+----+----+----+----+
bytes 0-31 :: SHA-256 Hash of bytes 32-527
bytes 32-526 :: random data
byte 527 :: reply
total length: 528
*/
type BuildResponseRecordELGamalAES [528]byte
type BuildResponseRecordELGamal [528]byte
type BuildResponseRecord struct {
Hash common.Hash
Padding [495]byte
Reply byte
}

24
lib/i2np/data.go Normal file
View File

@ -0,0 +1,24 @@
package i2np
/*
I2P I2NP Data
https://geti2p.net/spec/i2np
Accurate for version 0.9.28
+----+----+----+----+----+-//-+
| length | data... |
+----+----+----+----+----+-//-+
length ::
4 bytes
length of the payload
data ::
$length bytes
actual payload of this message
*/
type Data struct {
Length int
Data []byte
}

149
lib/i2np/database_lookup.go Normal file
View File

@ -0,0 +1,149 @@
package i2np
import (
"github.com/hkparker/go-i2p/lib/common"
)
/*
I2P I2NP DatabaseLookup
https://geti2p.net/spec/i2np
Accurate for version 0.9.28
+----+----+----+----+----+----+----+----+
| SHA256 hash as the key to look up |
+ +
| |
+ +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
| SHA256 hash of the routerInfo |
+ who is asking, or the gateway to +
| send the reply to |
+ +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
|flag| reply_tunnelId | size | |
+----+----+----+----+----+----+----+ +
| SHA256 of $key1 to exclude |
+ +
| |
+ +
| |
+ +----+
| | |
+----+----+----+----+----+----+----+ +
| SHA256 of $key2 to exclude |
+ +
~ ~
+ +----+
| | |
+----+----+----+----+----+----+----+ +
| |
+ +
| Session key if reply encryption |
+ was requested +
| |
+ +----+
| |tags|
+----+----+----+----+----+----+----+----+
| |
+ +
| Session tags if reply encryption |
+ was requested +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
key ::
32 bytes
SHA256 hash of the object to lookup
from ::
32 bytes
if deliveryFlag == 0, the SHA256 hash of the routerInfo entry this
request came from (to which the reply should be
sent)
if deliveryFlag == 1, the SHA256 hash of the reply tunnel gateway (to
which the reply should be sent)
flags ::
1 byte
bit order: 76543210
bit 0: deliveryFlag
0 => send reply directly
1 => send reply to some tunnel
bit 1: encryptionFlag
through release 0.9.5, must be set to 0
as of release 0.9.6, ignored
as of release 0.9.7:
0 => send unencrypted reply
1 => send AES encrypted reply using enclosed key and tag
bits 3-2: lookup type flags
through release 0.9.5, must be set to 00
as of release 0.9.6, ignored
as of release 0.9.16:
00 => normal lookup, return RouterInfo or LeaseSet or
DatabaseSearchReplyMessage
Not recommended when sending to routers
with version 0.9.16 or higher.
01 => LS lookup, return LeaseSet or
DatabaseSearchReplyMessage
10 => RI lookup, return RouterInfo or
DatabaseSearchReplyMessage
11 => exploration lookup, return DatabaseSearchReplyMessage
containing non-floodfill routers only (replaces an
excludedPeer of all zeroes)
bits 7-4:
through release 0.9.5, must be set to 0
as of release 0.9.6, ignored, set to 0 for compatibility with
future uses and with older routers
reply_tunnelId ::
4 byte TunnelID
only included if deliveryFlag == 1
tunnelId of the tunnel to send the reply to
size ::
2 byte Integer
valid range: 0-512
number of peers to exclude from the DatabaseSearchReplyMessage
excludedPeers ::
$size SHA256 hashes of 32 bytes each (total $size*32 bytes)
if the lookup fails, these peers are requested to be excluded
from the list in the DatabaseSearchReplyMessage.
if excludedPeers includes a hash of all zeroes, the request is
exploratory, and the DatabaseSearchReplyMessage is requested
to list non-floodfill routers only.
reply_key ::
32 byte SessionKey
only included if encryptionFlag == 1, only as of release 0.9.7
tags ::
1 byte Integer
valid range: 1-32 (typically 1)
the number of reply tags that follow
only included if encryptionFlag == 1, only as of release 0.9.7
reply_tags ::
one or more 32 byte SessionTags (typically one)
only included if encryptionFlag == 1, only as of release 0.9.7
*/
type DatabaseLookup struct {
Key common.Hash
From common.Hash
Flags byte
ReplyTunnelID [4]byte
Size int
ExcludedPeers []common.Hash
ReplyKey common.SessionKey
tags int
ReplyTags []common.SessionTag
}

View File

@ -0,0 +1,63 @@
package i2np
import (
"github.com/hkparker/go-i2p/lib/common"
)
/*
I2P I2NP DatabaseSearchReply
https://geti2p.net/spec/i2np
Accurate for version 0.9.28
+----+----+----+----+----+----+----+----+
| SHA256 hash as query key |
+ +
| |
+ +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
| num| peer_hashes |
+----+ +
| |
+ +
| |
+ +
| |
+ +----+----+----+----+----+----+----+
| | from |
+----+ +
| |
+ +
| |
+ +
| |
+ +----+----+----+----+----+----+----+
| |
+----+
key ::
32 bytes
SHA256 of the object being searched
num ::
1 byte Integer
number of peer hashes that follow, 0-255
peer_hashes ::
$num SHA256 hashes of 32 bytes each (total $num*32 bytes)
SHA256 of the RouterIdentity that the other router thinks is close
to the key
from ::
32 bytes
SHA256 of the RouterInfo of the router this reply was sent from
*/
type DatabaseSearchReply struct {
Key common.Hash
Count int
PeerHashes []common.Hash
From common.Hash
}

View File

@ -0,0 +1,96 @@
package i2np
import (
"github.com/hkparker/go-i2p/lib/common"
)
/*
I2P I2NP DatabaseStore
https://geti2p.net/spec/i2np
Accurate for version 0.9.28
with reply token:
+----+----+----+----+----+----+----+----+
| SHA256 Hash as key |
+ +
| |
+ +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
|type| reply token | reply_tunnelId
+----+----+----+----+----+----+----+----+
| SHA256 of the gateway RouterInfo |
+----+ +
| |
+ +
| |
+ +
| |
+ +----+----+----+----+----+----+----+
| | data ...
+----+-//
with reply token == 0:
+----+----+----+----+----+----+----+----+
| SHA256 Hash as key |
+ +
| |
+ +
| |
+ +
| |
+----+----+----+----+----+----+----+----+
|type| 0 | data ...
+----+----+----+----+----+-//
key ::
32 bytes
SHA256 hash
type ::
1 byte
type identifier
bit 0:
0 RouterInfo
1 LeaseSet
bits 7-1:
Through release 0.9.17, must be 0
As of release 0.9.18, ignored, reserved for future options, set to 0 for compatibility
reply token ::
4 bytes
If greater than zero, a DeliveryStatusMessage
is requested with the Message ID set to the value of the Reply Token.
A floodfill router is also expected to flood the data to the closest floodfill peers
if the token is greater than zero.
reply_tunnelId ::
4 byte TunnelId
Only included if reply token > 0
This is the TunnelId of the inbound gateway of the tunnel the response should be sent to
If $reply_tunnelId is zero, the reply is sent directy to the reply gateway router.
reply gateway ::
32 bytes
Hash of the RouterInfo entry to reach the gateway
Only included if reply token > 0
If $reply_tunnelId is nonzero, this is the router hash of the inbound gateway
of the tunnel the response should be sent to.
If $reply_tunnelId is zero, this is the router hash the response should be sent to.
data ::
If type == 0, data is a 2-byte Integer specifying the number of bytes that follow,
followed by a gzip-compressed RouterInfo.
If type == 1, data is an uncompressed LeaseSet.
*/
type DatabaseStore struct {
Key common.Hash
Type byte
ReplyToken [4]byte
ReplyTunnelID [4]byte
ReplyGateway common.Hash
Data []byte
}

View File

@ -0,0 +1,29 @@
package i2np
import (
"time"
)
/*
I2P I2NP DeliveryStatus
https://geti2p.net/spec/i2np
Accurate for version 0.9.28
+----+----+----+----+----+----+----+----+----+----+----+----+
| msg_id | time_stamp |
+----+----+----+----+----+----+----+----+----+----+----+----+
msg_id :: Integer
4 bytes
unique ID of the message we deliver the DeliveryStatus for (see
I2NPMessageHeader for details)
time_stamp :: Date
8 bytes
time the message was successfully created or delivered
*/
type DeliveryStatus struct {
MessageID int
Timestamp time.Time
}

72
lib/i2np/garlic.go Normal file
View File

@ -0,0 +1,72 @@
package i2np
import (
"github.com/hkparker/go-i2p/lib/common"
"time"
)
/*
I2P I2NP Garlic
https://geti2p.net/spec/i2np
Accurate for version 0.9.28
Encrypted:
+----+----+----+----+----+----+----+----+
| length | data |
+----+----+----+----+ +
| |
~ ~
~ ~
| |
+----+----+----+----+----+----+----+----+
length ::
4 byte Integer
number of bytes that follow 0 - 64 KB
data ::
$length bytes
ElGamal encrypted data
Unencrypted data:
+----+----+----+----+----+----+----+----+
| num| clove 1 |
+----+ +
| |
~ ~
~ ~
| |
+----+----+----+----+----+----+----+----+
| clove 2 ... |
~ ~
~ ~
| |
+----+----+----+----+----+----+----+----+
| Certificate | Message_ID |
+----+----+----+----+----+----+----+----+
Expiration |
+----+----+----+----+----+----+----+
num ::
1 byte Integer number of GarlicCloves to follow
clove :: a GarlicClove
Certificate :: always NULL in the current implementation (3 bytes total, all zeroes)
Message_ID :: 4 byte Integer
Expiration :: Date (8 bytes)
*/
type GarlicElGamal []byte
type Garlic struct {
Count int
Cloves []GarlicClove
Certificate common.Certificate
MessageID int
Expiration time.Time
}

49
lib/i2np/garlic_clove.go Normal file
View File

@ -0,0 +1,49 @@
package i2np
import (
"github.com/hkparker/go-i2p/lib/common"
"time"
)
/*
I2P I2NP GarlicClove
https://geti2p.net/spec/i2np
Accurate for version 0.9.28
Unencrypted:
+----+----+----+----+----+----+----+----+
| Delivery Instructions |
~ ~
~ ~
| |
+----+----+----+----+----+----+----+----+
| I2NP Message |
~ ~
~ ~
| |
+----+----+----+----+----+----+----+----+
| Clove ID | Expiration
+----+----+----+----+----+----+----+----+
| Certificate |
+----+----+----+----+----+----+----+
Delivery Instructions :: as defined below
Length varies but is typically 1, 33, or 37 bytes
I2NP Message :: Any I2NP Message
Clove ID :: 4 byte Integer
Expiration :: Date (8 bytes)
Certificate :: Always NULL in the current implementation (3 bytes total, all zeroes)
*/
type GarlicClove struct {
DeliveryInstructions GarlicCloveDeliveryInstructions
I2NPMessage I2NPMessage
CloveID int
Expiration time.Time
Certificate common.Certificate
}

View File

@ -0,0 +1,80 @@
package i2np
import (
"github.com/hkparker/go-i2p/lib/common"
"github.com/hkparker/go-i2p/lib/tunnel"
)
/*
I2P I2NP GarlicCloveDeliveryInstructions
https://geti2p.net/spec/i2np
Accurate for version 0.9.28
+----+----+----+----+----+----+----+----+
|flag| |
+----+ +
| |
+ Session Key (optional) +
| |
+ +
| |
+ +----+----+----+----+--------------+
| | |
+----+ +
| |
+ To Hash (optional) +
| |
+ +
| |
+ +----+----+----+----+--------------+
| | Tunnel ID (opt) | Delay (opt)
+----+----+----+----+----+----+----+----+
|
+----+
flag ::
1 byte
Bit order: 76543210
bit 7: encrypted? Unimplemented, always 0
If 1, a 32-byte encryption session key is included
bits 6-5: delivery type
0x0 = LOCAL, 0x01 = DESTINATION, 0x02 = ROUTER, 0x03 = TUNNEL
bit 4: delay included? Not fully implemented, always 0
If 1, four delay bytes are included
bits 3-0: reserved, set to 0 for compatibility with future uses
Session Key ::
32 bytes
Optional, present if encrypt flag bit is set.
Unimplemented, never set, never present.
To Hash ::
32 bytes
Optional, present if delivery type is DESTINATION, ROUTER, or TUNNEL
If DESTINATION, the SHA256 Hash of the destination
If ROUTER, the SHA256 Hash of the router
If TUNNEL, the SHA256 Hash of the gateway router
Tunnel ID :: TunnelId
4 bytes
Optional, present if delivery type is TUNNEL
The destination tunnel ID
Delay :: Integer
4 bytes
Optional, present if delay included flag is set
Not fully implemented. Specifies the delay in seconds.
Total length: Typical length is:
1 byte for LOCAL delivery;
33 bytes for ROUTER / DESTINATION delivery;
37 bytes for TUNNEL delivery
*/
type GarlicCloveDeliveryInstructions struct {
Flag byte
SessionKey common.SessionKey
Hash common.Hash
TunnelID tunnel.TunnelID
Delay int
}

281
lib/i2np/header.go Normal file
View File

@ -0,0 +1,281 @@
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 I2NPNTCPHeader struct {
Type int
MessageID int
Expiration time.Time
Size int
Checksum int
Data []byte
}
type I2NPSSUHeader struct {
Type int
Expiration time.Time
}
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) (I2NPNTCPHeader, error) {
header := I2NPNTCPHeader{}
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 := ReadI2NPNTCPMessageExpiration(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
}
message_data, err := ReadI2NPNTCPData(data, header.Size)
if err != nil {
return header, err
} else {
header.Data = message_data
}
log.WithFields(log.Fields{
"at": "i2np.ReadI2NPNTCPHeader",
"error": err.Error(),
}).Debug("parsed_i2np_ntcp_header")
return header, nil
}
func ReadI2NPSSUHeader(data []byte) (I2NPSSUHeader, error) {
header := I2NPSSUHeader{}
message_type, err := ReadI2NPType(data)
if err != nil {
return header, err
} else {
header.Type = message_type
}
message_date, err := ReadI2NPSSUMessageExpiration(data)
if err != nil {
return header, err
} else {
header.Expiration = message_date.Time()
}
return header, 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 ReadI2NPNTCPMessageExpiration(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.ReadI2NPNTCPMessageExpiration",
"date": date,
}).Debug("parsed_i2np_message_date")
return date, nil
}
func ReadI2NPSSUMessageExpiration(data []byte) (common.Date, error) {
if len(data) < 5 {
return common.Date{}, ERR_I2NP_NOT_ENOUGH_DATA
}
date := common.Date{}
copy(date[4:], data[1:5])
log.WithFields(log.Fields{
"at": "i2np.ReadI2NPSSUMessageExpiration",
"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
}
func ReadI2NPNTCPData(data []byte, size int) ([]byte, error) {
if len(data) < 16+size {
return []byte{}, ERR_I2NP_NOT_ENOUGH_DATA
}
return data[16 : 16+size], nil
}

View File

@ -38,18 +38,34 @@ func TestReadI2NPNTCPMessageIDWithValidData(t *testing.T) {
assert.Nil(err) assert.Nil(err)
} }
func TestReadI2NPNTCPMessageDateWithMissingData(t *testing.T) { func TestReadI2NPNTCPMessageExpirationWithMissingData(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
date, err := ReadI2NPNTCPMessageDate([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) date, err := ReadI2NPNTCPMessageExpiration([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
assert.Equal(common.Date{}, date) assert.Equal(common.Date{}, date)
assert.Equal(ERR_I2NP_NOT_ENOUGH_DATA, err) assert.Equal(ERR_I2NP_NOT_ENOUGH_DATA, err)
} }
func TestReadI2NPNTCPMessageDateWithValidData(t *testing.T) { func TestReadI2NPNTCPMessageExpirationWithValidData(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
date, err := ReadI2NPNTCPMessageDate([]byte{0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x05, 0x26, 0x5c, 0x00}) date, err := ReadI2NPNTCPMessageExpiration([]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 TestReadI2NPSSUMessageExpirationWithMissingData(t *testing.T) {
assert := assert.New(t)
date, err := ReadI2NPSSUMessageExpiration([]byte{0x00, 0x00, 0x00, 0x00})
assert.Equal(common.Date{}, date)
assert.Equal(ERR_I2NP_NOT_ENOUGH_DATA, err)
}
func TestReadI2NPSSUMessageExpirationWithValidData(t *testing.T) {
assert := assert.New(t)
date, err := ReadI2NPSSUMessageExpiration([]byte{0x01, 0x05, 0x26, 0x5c, 0x00})
assert.Equal(int64(86400), date.Time().Unix()) assert.Equal(int64(86400), date.Time().Unix())
assert.Nil(err) assert.Nil(err)
} }
@ -85,3 +101,35 @@ func TestReadI2NPNTCPMessageChecksumWithValidData(t *testing.T) {
assert.Equal(1, checksum) assert.Equal(1, checksum)
assert.Nil(err) assert.Nil(err)
} }
func TestReadI2NPNTCPDataWithNoData(t *testing.T) {
assert := assert.New(t)
data, err := ReadI2NPNTCPData([]byte{}, 3)
assert.Equal([]byte{}, data)
assert.Equal(ERR_I2NP_NOT_ENOUGH_DATA, err)
}
func TestReadI2NPNTCPDataWithMissingData(t *testing.T) {
assert := assert.New(t)
data, err := ReadI2NPNTCPData([]byte{0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x05, 0x26, 0x5c, 0x00, 0x00, 0x03, 0x01, 0x01, 0x02}, 3)
assert.Equal([]byte{}, data)
assert.Equal(ERR_I2NP_NOT_ENOUGH_DATA, err)
}
func TestReadI2NPNTCPDataWithExtraData(t *testing.T) {
assert := assert.New(t)
data, err := ReadI2NPNTCPData([]byte{0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x05, 0x26, 0x5c, 0x00, 0x00, 0x03, 0x01, 0x01, 0x02, 0x03, 0x04}, 3)
assert.Equal([]byte{0x01, 0x02, 0x03}, data)
assert.Nil(err)
}
func TestReadI2NPNTCPDataWithValidData(t *testing.T) {
assert := assert.New(t)
data, err := ReadI2NPNTCPData([]byte{0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x05, 0x26, 0x5c, 0x00, 0x00, 0x03, 0x01, 0x01, 0x02, 0x03}, 3)
assert.Equal([]byte{0x01, 0x02, 0x03}, data)
assert.Nil(err)
}

View File

@ -1,228 +1,3 @@
package i2np 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 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
}

28
lib/i2np/tunnel_build.go Normal file
View File

@ -0,0 +1,28 @@
package i2np
/*
I2P I2NP TunnelBuild
https://geti2p.net/spec/i2np
Accurate for version 0.9.28
+----+----+----+----+----+----+----+----+
| Record 0 ... |
| |
+----+----+----+----+----+----+----+----+
| Record 1 ... |
~ ..... ~
| |
+----+----+----+----+----+----+----+----+
| Record 7 ... |
| |
+----+----+----+----+----+----+----+----+
Just 8 BuildRequestRecords attached together
record size: 528 bytes
total size: 8*528 = 4224 bytes
*/
type TunnelBuild [8]BuildRequestRecord

View File

@ -0,0 +1,11 @@
package i2np
/*
I2P I2NP TunnelBuildReply
https://geti2p.net/spec/i2np
Accurate for version 0.9.28
Same format as TunnelBuildMessage, with BuildResponseRecords
*/
type TunnelBuildReply [8]BuildResponseRecord

29
lib/i2np/tunnel_data.go Normal file
View File

@ -0,0 +1,29 @@
package i2np
/*
I2P I2NP TunnelData
https://geti2p.net/spec/i2np
Accurate for version 0.9.28
+----+----+----+----+----+----+----+----+
| tunnnelID | data |
+----+----+----+----+ |
| |
~ ~
~ ~
| |
+ +----+----+----+----+
| |
+----+----+----+----+
tunnelId ::
4 byte TunnelId
identifies the tunnel this message is directed at
data ::
1024 bytes
payload data.. fixed to 1024 bytes
*/
type TunnelData [1028]byte

View File

@ -0,0 +1,33 @@
package i2np
import (
"github.com/hkparker/go-i2p/lib/tunnel"
)
/*
I2P I2NP TunnelGateway
https://geti2p.net/spec/i2np
Accurate for version 0.9.28
+----+----+----+----+----+----+----+-//
| tunnelId | length | data...
+----+----+----+----+----+----+----+-//
tunnelId ::
4 byte TunnelId
identifies the tunnel this message is directed at
length ::
2 byte Integer
length of the payload
data ::
$length bytes
actual payload of this message
*/
type TunnelGatway struct {
TunnelID tunnel.TunnelID
Length int
Data []byte
}

View File

@ -0,0 +1,26 @@
package i2np
/*
I2P I2NP VariableTunnelBuild
https://geti2p.net/spec/i2np
Accurate for version 0.9.28
+----+----+----+----+----+----+----+----+
| num| BuildRequestRecords...
+----+----+----+----+----+----+----+----+
Same format as TunnelBuildMessage, except for the addition of a $num field
in front and $num number of BuildRequestRecords instead of 8
num ::
1 byte Integer
Valid values: 1-8
record size: 528 bytes
total size: 1+$num*528
*/
type VariableTunnelBuild struct {
Count int
BuildRequestRecords []BuildRequestRecord
}

View File

@ -0,0 +1,18 @@
package i2np
/*
I2P I2NP VariableTunnelBuildReply
https://geti2p.net/spec/i2np
Accurate for version 0.9.28
+----+----+----+----+----+----+----+----+
| num| BuildResponseRecords...
+----+----+----+----+----+----+----+----+
Same format as VariableTunnelBuildMessage, with BuildResponseRecords.
*/
type VariableTunnelBuildReply struct {
Count int
BuildResponseRecords []BuildResponseRecord
}