mirror of
https://github.com/go-i2p/go-i2p.git
synced 2025-07-05 06:05:16 -04:00
i2np structs
This commit is contained in:
163
lib/i2np/build_request_record.go
Normal file
163
lib/i2np/build_request_record.go
Normal 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
|
||||||
|
}
|
48
lib/i2np/build_response_record.go
Normal file
48
lib/i2np/build_response_record.go
Normal 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
24
lib/i2np/data.go
Normal 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
149
lib/i2np/database_lookup.go
Normal 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
|
||||||
|
}
|
63
lib/i2np/database_search_reply.go
Normal file
63
lib/i2np/database_search_reply.go
Normal 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
|
||||||
|
}
|
96
lib/i2np/database_store.go
Normal file
96
lib/i2np/database_store.go
Normal 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
|
||||||
|
}
|
29
lib/i2np/delivery_status.go
Normal file
29
lib/i2np/delivery_status.go
Normal 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
72
lib/i2np/garlic.go
Normal 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
49
lib/i2np/garlic_clove.go
Normal 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
|
||||||
|
}
|
80
lib/i2np/garlic_clove_delivery_instructions.go
Normal file
80
lib/i2np/garlic_clove_delivery_instructions.go
Normal 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
281
lib/i2np/header.go
Normal 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
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
225
lib/i2np/i2np.go
225
lib/i2np/i2np.go
@ -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
28
lib/i2np/tunnel_build.go
Normal 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
|
11
lib/i2np/tunnel_build_reply.go
Normal file
11
lib/i2np/tunnel_build_reply.go
Normal 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
29
lib/i2np/tunnel_data.go
Normal 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
|
33
lib/i2np/tunnel_gateway.go
Normal file
33
lib/i2np/tunnel_gateway.go
Normal 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
|
||||||
|
}
|
26
lib/i2np/variable_tunnel_build.go
Normal file
26
lib/i2np/variable_tunnel_build.go
Normal 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
|
||||||
|
}
|
18
lib/i2np/variable_tunnel_build_reply.go
Normal file
18
lib/i2np/variable_tunnel_build_reply.go
Normal 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
|
||||||
|
}
|
Reference in New Issue
Block a user