Files
Go_I2p/lib/tunnel/delivery.go
2021-04-19 20:51:15 -04:00

814 lines
24 KiB
Go

package tunnel
import (
"encoding/binary"
"errors"
"github.com/go-i2p/go-i2p/lib/common"
log "github.com/sirupsen/logrus"
)
/*
I2P First Fragment Delivery Instructions
https://geti2p.net/spec/tunnel-message#struct-tunnelmessagedeliveryinstructions
Accurate for version 0.9.11
+----+----+----+----+----+----+----+----+
|flag| Tunnel ID (opt) | |
+----+----+----+----+----+ +
| |
+ +
| To Hash (optional) |
+ +
| |
+ +--------------+
| |dly | Message
+----+----+----+----+----+----+----+----+
ID (opt) |extended opts (opt)| size |
+----+----+----+----+----+----+----+----+
flag ::
1 byte
Bit order: 76543210
bit 7: 0 to specify an initial fragment or an unfragmented message
bits 6-5: delivery type
0x0 = LOCAL
0x01 = TUNNEL
0x02 = ROUTER
0x03 = unused, invalid
Note: LOCAL is used for inbound tunnels only, unimplemented
for outbound tunnels
bit 4: delay included? Unimplemented, always 0
If 1, a delay byte is included
bit 3: fragmented? If 0, the message is not fragmented, what follows
is the entire message
If 1, the message is fragmented, and the
instructions contain a Message ID
bit 2: extended options? Unimplemented, always 0
If 1, extended options are included
bits 1-0: reserved, set to 0 for compatibility with future uses
Tunnel ID :: TunnelId
4 bytes
Optional, present if delivery type is TUNNEL
The destination tunnel ID
To Hash ::
32 bytes
Optional, present if delivery type is ROUTER, or TUNNEL See: https://trac.i2p2.de/ticket/1845#ticket
If ROUTER, the SHA256 Hash of the router
If TUNNEL, the SHA256 Hash of the gateway router
Delay ::
1 byte
Optional, present if delay included flag is set
In tunnel messages: Unimplemented, never present; original
specification:
bit 7: type (0 = strict, 1 = randomized)
bits 6-0: delay exponent (2^value minutes)
Message ID ::
4 bytes
Optional, present if this message is the first of 2 or more fragments
(i.e. if the fragmented bit is 1)
An ID that uniquely identifies all fragments as belonging to a single
message (the current implementation uses I2NPMessageHeader.msg_id)
Extended Options ::
2 or more bytes
Optional, present if extend options flag is set
Unimplemented, never present; original specification:
One byte length and then that many bytes
size ::
2 bytes
The length of the fragment that follows
Valid values: 1 to approx. 960 in a tunnel message
Total length: Typical length is:
3 bytes for LOCAL delivery (tunnel message);
35 bytes for ROUTER / DESTINATION delivery or 39 bytes for TUNNEL
delivery (unfragmented tunnel message);
39 bytes for ROUTER delivery or 43 bytes for TUNNEL delivery (first
fragment)
I2P Follow-on Fragment Delivery Instructions
https://geti2p.net/spec/tunnel-message#struct-tunnelmessagedeliveryinstructions
Accurate for version 0.9.11
----+----+----+----+----+----+----+
|frag| Message ID | size |
+----+----+----+----+----+----+----+
frag ::
1 byte
Bit order: 76543210
binary 1nnnnnnd
bit 7: 1 to indicate this is a follow-on fragment
bits 6-1: nnnnnn is the 6 bit fragment number from 1 to 63
bit 0: d is 1 to indicate the last fragment, 0 otherwise
Message ID ::
4 bytes
Identifies the fragment sequence that this fragment belongs to.
This will match the message ID of an initial fragment (a fragment
with flag bit 7 set to 0 and flag bit 3 set to 1).
size ::
2 bytes
the length of the fragment that follows
valid values: 1 to 996
total length: 7 bytes
*/
const (
DT_LOCAL = iota
DT_TUNNEL
DT_ROUTER
DT_UNUSED
)
const (
FIRST_FRAGMENT = iota
FOLLOW_ON_FRAGMENT
)
const (
FLAG_SIZE = 1
TUNNEL_ID_SIZE = 4
HASH_SIZE = 32
DELAY_SIZE = 1
MESSAGE_ID_SIZE = 4
EXTENDED_OPTIONS_MIN_SIZE = 2
SIZE_FIELD_SIZE = 2
)
type DelayFactor byte
type DeliveryInstructions []byte
// Return if the DeliveryInstructions are of type FIRST_FRAGMENT or FOLLOW_ON_FRAGMENT.
func (delivery_instructions DeliveryInstructions) Type() (int, error) {
if len(delivery_instructions) >= 1 {
/*
Check if the 7 bit of the Delivery Instructions
is set using binary AND operator to determine
the Delivery Instructions type
1xxxxxxx 0xxxxxxx
&10000000 &10000000
--------- ---------
10000000 00000000
bit is set, bit is not set,
message is a message is an
follow-on fragment initial I2NP message
fragment or a complete fragment
*/
if (delivery_instructions[0] & 0x08) == 0x08 {
return FOLLOW_ON_FRAGMENT, nil
}
return FIRST_FRAGMENT, nil
}
return 0, errors.New("DeliveryInstructions contains no data")
}
// Read the integer stored in the 6-1 bits of a FOLLOW_ON_FRAGMENT's flag, indicating
// the fragment number.
func (delivery_instructions DeliveryInstructions) FragmentNumber() (int, error) {
di_type, err := delivery_instructions.Type()
if err != nil {
return 0, err
}
/*
Read the 6-1 bits of the Delivery Instructions
to determine the FragmentNumber of Follow On Fragments
xnnnnnnx
&01111110 bit shift
---------
0??????0 >> 1 => Integer(??????)
*/
if di_type == FOLLOW_ON_FRAGMENT {
return common.Integer(
[]byte{((delivery_instructions[0] & 0x7e) >> 1)},
), nil
}
return 0, errors.New("Fragment Number only exists on FOLLOW_ON_FRAGMENT Delivery Instructions")
}
// Read the value of the 0 bit of a FOLLOW_ON_FRAGMENT, which is set to 1 to indicate the
// last fragment.
func (delivery_instructions DeliveryInstructions) LastFollowOnFragment() (bool, error) {
di_type, err := delivery_instructions.Type()
if err != nil {
return false, err
}
/*
Check the 0 bit of the Delivery Instructions
to determine if this is the last Follow On Fragment
xxxxxxxx
&00000001
---------
0000000? => n
*/
if di_type == FOLLOW_ON_FRAGMENT {
if delivery_instructions[0]&0x01 == 0x01 {
return true, nil
} else {
return false, nil
}
}
return false, errors.New("Last Fragment only exists for FOLLOW_ON_FRAGMENT Delivery Instructions")
}
// Return the delivery type for these DeliveryInstructions, can be of type
// DT_LOCAL, DT_TUNNEL, DT_ROUTER, or DT_UNUSED.
func (delivery_instructions DeliveryInstructions) DeliveryType() (byte, error) {
if len(delivery_instructions) >= 1 {
/*
Check if the 6-5 bits of the Delivery Instructions
are set using binary AND operator to determine
the delivery type
xx0?xxxx
&00110000 bit shift
---------
000?0000 >> 4 => n (DT_* consts)
*/
return ((delivery_instructions[0] & 0x30) >> 4), nil
}
return 0, errors.New("DeliveryInstructions contains no data")
}
// Check if the delay bit is set. This feature in unimplemented in the Java router.
func (delivery_instructions DeliveryInstructions) HasDelay() (bool, error) {
if len(delivery_instructions) >= 1 {
/*
Check if the 4 bit of the Delivery Instructions
is set using binary AND operator to determine
if the Delivery Instructions has a delay
xxx1xxxx xxx0xxxx
&00010000 &00010000
--------- ---------
00010000 00000000
bit is set, bit is not set,
delay is included no delay included
Delay is unimplemented in the Java router, a warning
is logged as this is interesting behavior.
*/
delay := (delivery_instructions[0] & 0x10) == 0x10
if delay {
log.WithFields(log.Fields{
"at": "(DeliveryInstructions) HasDelay",
"info": "this feature is unimplemented in the Java router",
}).Warn("DeliveryInstructions found with delay bit set")
}
return delay, nil
}
return false, errors.New("DeliveryInstructions contains no data")
}
// Returns true if the Delivery Instructions are fragmented or false
// if the following data contains the entire message
func (delivery_instructions DeliveryInstructions) Fragmented() (bool, error) {
if len(delivery_instructions) >= 1 {
/*
Check if the 3 bit of the Delivery Instructions
is set using binary AND operator to determine
if the Delivery Instructions is fragmented or if
the entire message is contained in the following data
xxxx1xxx xxxx0xxx
&00001000 &00001000
--------- ---------
00001000 00000000
bit is set, bit is not set,
message is message is not
fragmented fragmented
*/
return ((delivery_instructions[0] & 0x08) == 0x08), nil
}
return false, errors.New("DeliveryInstructions contains no data")
}
// Check if the extended options bit is set. This feature in unimplemented in the Java router.
func (delivery_instructions DeliveryInstructions) HasExtendedOptions() (bool, error) {
if len(delivery_instructions) >= 1 {
/*
Check if the 2 bit of the Delivery Instructions
is set using binary AND operator to determine
if the Delivery Instructions has a extended options
xxxxx1xx xxxxx0xx
&00000100 &00000100
--------- ---------
00000100 00000000
bit is set, bit is not set,
extended options extended options
included not included
Extended options is unimplemented in the Java router, a warning
is logged as this is interesting behavior.
*/
extended_options := (delivery_instructions[0] & 0x04) == 0x04
if extended_options {
log.WithFields(log.Fields{
"at": "(DeliveryInstructions) ExtendedOptions",
"info": "this feature is unimplemented in the Java router",
}).Warn("DeliveryInstructions found with extended_options bit set")
}
return extended_options, nil
}
return false, errors.New("DeliveryInstructions contains no data")
}
// Check if the DeliveryInstructions is of type DT_TUNNEL.
func (delivery_instructions DeliveryInstructions) HasTunnelID() (bool, error) {
di_type, err := delivery_instructions.DeliveryType()
if err != nil {
return false, err
}
return di_type == DT_TUNNEL, nil
}
func (delivery_instructions DeliveryInstructions) HasHash() (bool, error) {
di_type, err := delivery_instructions.DeliveryType()
if err != nil {
return false, err
}
if di_type == DT_TUNNEL || di_type == DT_ROUTER {
min_size := FLAG_SIZE + HASH_SIZE
if di_type == DT_TUNNEL {
min_size += TUNNEL_ID_SIZE
}
if len(delivery_instructions) < min_size {
return false, errors.New("Delivery Instructions indicates hash present but has too little data")
}
} else {
return false, nil
}
return true, nil
}
// Return the tunnel ID in this DeliveryInstructions or 0 and an error if the
// DeliveryInstructions are not of type DT_TUNNEL.
func (delivery_instructions DeliveryInstructions) TunnelID() (tunnel_id uint32, err error) {
has_tunnel_id, err := delivery_instructions.HasTunnelID()
if err != nil {
return
}
if has_tunnel_id {
if len(delivery_instructions) >= FLAG_SIZE+TUNNEL_ID_SIZE {
tunnel_id = binary.BigEndian.Uint32(delivery_instructions[FLAG_SIZE:TUNNEL_ID_SIZE])
} else {
err = errors.New("DeliveryInstructions are invalid, too little data for Tunnel ID")
}
} else {
err = errors.New("DeliveryInstructions are not of type DT_TUNNEL")
}
return
}
// Return the hash for these DeliveryInstructions, which varies by hash type.
// If the type is DT_TUNNEL, hash is the SHA256 of the gateway router, if
// the type is DT_ROUTER it is the SHA256 of the router.
func (delivery_instructions DeliveryInstructions) Hash() (hash common.Hash, err error) {
delivery_type, err := delivery_instructions.DeliveryType()
if err != nil {
return
}
hash_start := FLAG_SIZE
hash_end := FLAG_SIZE + HASH_SIZE
if delivery_type == DT_TUNNEL {
hash_start := hash_start + TUNNEL_ID_SIZE
hash_end := hash_end + TUNNEL_ID_SIZE
if len(delivery_instructions) >= hash_end {
copy(hash[:], delivery_instructions[hash_start:hash_end])
} else {
err = errors.New("DeliveryInstructions is invalid, not contain enough data for hash given type DT_TUNNEL")
}
} else if delivery_type == DT_ROUTER {
if len(delivery_instructions) >= hash_end {
copy(hash[:], delivery_instructions[hash_start:hash_end])
} else {
err = errors.New("DeliveryInstructions is invalid, not contain enough data for hash given type DT_ROUTER")
}
} else {
err = errors.New("No Hash on DeliveryInstructions not of type DT_TUNNEL or DT_ROUTER")
}
return
}
// Return the DelayFactor if present and any errors encountered parsing the DeliveryInstructions.
func (delivery_instructions DeliveryInstructions) Delay() (delay_factor DelayFactor, err error) {
delay, err := delivery_instructions.HasDelay()
if err != nil {
return
}
if delay {
var di_type byte
di_type, err = delivery_instructions.DeliveryType()
if err != nil {
return
}
if di_type == DT_TUNNEL {
if len(delivery_instructions) >= FLAG_SIZE+TUNNEL_ID_SIZE+HASH_SIZE {
delay_factor = DelayFactor(delivery_instructions[FLAG_SIZE+TUNNEL_ID_SIZE+HASH_SIZE])
} else {
err = errors.New("DeliveryInstructions is invalid, does not contain enough data for DelayFactor")
return
}
} else if di_type == DT_ROUTER {
if len(delivery_instructions) >= FLAG_SIZE+HASH_SIZE {
delay_factor = DelayFactor(delivery_instructions[FLAG_SIZE+HASH_SIZE])
} else {
err = errors.New("DeliveryInstructions is invalid, does not contain enough data for DelayFactor")
return
}
} else {
log.WithFields(log.Fields{
"at": "(DeliveryInstructions) Delay",
}).Warn("Delay not present on DeliveryInstructions not of type DT_TUNNEL or DT_ROUTER")
}
}
return
}
// Return the I2NP Message ID or 0 and an error if the data is not available for this
// DeliveryInstructions.
func (delivery_instructions DeliveryInstructions) MessageID() (msgid uint32, err error) {
di_type, err := delivery_instructions.Type()
if err != nil {
return
}
if di_type == FOLLOW_ON_FRAGMENT {
if len(delivery_instructions) >= 5 {
msgid = binary.BigEndian.Uint32(delivery_instructions[1:5])
} else {
err = errors.New("DeliveryInstructions are invalid, not enough data for Message ID")
}
} else if di_type == FIRST_FRAGMENT {
var message_id_index int
message_id_index, err = delivery_instructions.message_id_index()
if err != nil {
return
}
if len(delivery_instructions) >= message_id_index+4 {
msgid = binary.BigEndian.Uint32(delivery_instructions[message_id_index : message_id_index+4])
} else {
err = errors.New("DeliveryInstructions are invalid, not enough data for Message ID")
}
} else {
err = errors.New("No Message ID for DeliveryInstructions not of type FIRST_FRAGMENT or FOLLOW_ON_FRAGMENT")
}
return
}
// Return the Extended Options data if present, or an error if not present. Extended Options in unimplemented
// in the Java router and the presence of extended options will generate a warning.
func (delivery_instructions DeliveryInstructions) ExtendedOptions() (data []byte, err error) {
ops, err := delivery_instructions.HasExtendedOptions()
if err != nil {
return
}
if ops {
var extended_options_index int
extended_options_index, err = delivery_instructions.extended_options_index()
if err != nil {
return
}
if len(delivery_instructions) < extended_options_index+2 {
err = errors.New("DeliveryInstructions are invalid, length is shorter than required for Extended Options")
return
} else {
extended_options_size := common.Integer([]byte{delivery_instructions[extended_options_index]})
if len(delivery_instructions) < extended_options_index+1+extended_options_size {
err = errors.New("DeliveryInstructions are invalid, length is shorter than specified in Extended Options")
return
} else {
data = delivery_instructions[extended_options_index+1 : extended_options_size]
return
}
}
} else {
err = errors.New("DeliveryInstruction does not have the ExtendedOptions flag set")
}
return
}
// Return the size of the associated I2NP fragment and an error if the data is unavailable.
func (delivery_instructions DeliveryInstructions) FragmentSize() (frag_size uint16, err error) {
di_type, err := delivery_instructions.Type()
if err != nil {
return
}
if di_type == FOLLOW_ON_FRAGMENT {
if len(delivery_instructions) >= 7 {
frag_size = binary.BigEndian.Uint16(delivery_instructions[5:7])
} else {
err = errors.New("DeliveryInstructions are invalid, not enough data for Fragment Size")
}
} else if di_type == FIRST_FRAGMENT {
var fragment_size_index int
fragment_size_index, err = delivery_instructions.fragment_size_index()
if err != nil {
return
}
if len(delivery_instructions) >= fragment_size_index+2 {
frag_size = binary.BigEndian.Uint16(delivery_instructions[fragment_size_index : fragment_size_index+2])
} else {
err = errors.New("DeliveryInstructions are invalid, not enough data for Fragment Size")
}
} else {
err = errors.New("No Fragment Size for DeliveryInstructions not of type FIRST_FRAGMENT or FOLLOW_ON_FRAGMENT")
}
return
}
// Find the correct index for the Message ID in a FIRST_FRAGMENT DeliveryInstructions
func (delivery_instructions DeliveryInstructions) message_id_index() (message_id int, err error) {
fragmented, err := delivery_instructions.Fragmented()
if err != nil {
return
}
if fragmented {
// Start counting after the flags
message_id = 1
// Add the Tunnel ID and Hash if present
var di_type byte
di_type, err = delivery_instructions.DeliveryType()
if err != nil {
return
}
if di_type == DT_TUNNEL {
message_id += 36
} else if di_type == DT_ROUTER {
message_id += 32
}
// Add the Delay if present
var delay bool
delay, err = delivery_instructions.HasDelay()
if err != nil {
return
}
if delay {
message_id++
}
return message_id, nil
} else {
return 0, errors.New("DeliveryInstruction must be fragmented to have a Message ID")
}
}
// Find the index of the extended options in this Delivery Instruction, if they exist.
func (delivery_instructions DeliveryInstructions) extended_options_index() (extended_options int, err error) {
ops, err := delivery_instructions.HasExtendedOptions()
if err != nil {
return
}
if ops {
// Start counting after the flags
extended_options = 1
// Add the Tunnel ID and Hash if present
var di_type byte
di_type, err = delivery_instructions.DeliveryType()
if err != nil {
return
}
if di_type == DT_TUNNEL {
extended_options += 36
} else if di_type == DT_ROUTER {
extended_options += 32
}
// Add the Delay if present
var delay bool
delay, err = delivery_instructions.HasDelay()
if err != nil {
return
}
if delay {
extended_options++
}
// add message id if present
if _, err = delivery_instructions.MessageID(); err == nil {
extended_options += 4
} else {
err = nil
}
return extended_options, nil
} else {
err = errors.New("DeliveryInstruction does not have the ExtendedOptions flag set")
}
return
}
// Find the index of the Fragment Size data in this Delivery Instruction.
func (delivery_instructions DeliveryInstructions) fragment_size_index() (fragment_size int, err error) {
// Start counting after the flags
fragment_size = 1
// Add the Tunnel ID and Hash if present
var di_type byte
di_type, err = delivery_instructions.DeliveryType()
if err != nil {
return
}
if di_type == DT_TUNNEL {
fragment_size += 36
} else if di_type == DT_ROUTER {
fragment_size += 32
}
// Add the Delay if present
var delay bool
delay, err = delivery_instructions.HasDelay()
if err != nil {
return
}
if delay {
fragment_size++
}
// add the message id if present
if _, err = delivery_instructions.MessageID(); err == nil {
fragment_size += 4
} else {
err = nil
}
// add extended options if present
if opts, err := delivery_instructions.HasExtendedOptions(); opts && err != nil {
if extended_opts, err := delivery_instructions.ExtendedOptions(); err == nil {
fragment_size += len(extended_opts) + 1
}
}
return fragment_size, nil
}
func maybeAppendTunnelID(data, current []byte) (now []byte, err error) {
if has_tunnel_id, _ := DeliveryInstructions(data).HasTunnelID(); has_tunnel_id {
_, err = DeliveryInstructions(data).TunnelID()
if err == nil {
now = append(current, data[1:5]...)
} else {
return
}
}
return
}
func maybeAppendHash(di_flag DeliveryInstructions, data, current []byte) (now []byte, err error) {
delivery_type, _ := di_flag.DeliveryType()
if _, err := DeliveryInstructions(data).HasHash(); err == nil {
hash_start := 1
hash_end := 33
if delivery_type == DT_TUNNEL {
hash_start = hash_start + 4
hash_end = hash_end + 4
}
if err == nil {
now = append(current, data[hash_start:hash_end]...)
}
}
return
}
func maybeAppendDelay(di_flag DeliveryInstructions, data, current []byte) (now []byte, err error) {
delivery_type, _ := di_flag.DeliveryType()
if _, err = DeliveryInstructions(data).HasHash(); err == nil {
delay_start := 1
if delivery_type == DT_TUNNEL {
delay_start = delay_start + 4
}
if hash, _ := di_flag.Hash(); len(hash) == 32 {
delay_start = delay_start + 32
}
if err == nil {
now = append(current, data[delay_start])
}
}
return
}
func maybeAppendMessageID(di_flag DeliveryInstructions, di_type int, data, current []byte) (now []byte, err error) {
if di_type == FIRST_FRAGMENT {
if fragmented, _ := di_flag.Fragmented(); fragmented {
message_id_index := 1
if dtype, _ := di_flag.DeliveryType(); dtype == DT_TUNNEL {
message_id_index += 4
}
if dtype, _ := di_flag.DeliveryType(); dtype == DT_TUNNEL || dtype == DT_ROUTER {
message_id_index += 32
}
if delay, _ := di_flag.HasDelay(); delay {
message_id_index += 1
}
if len(data) < message_id_index+4 {
err = errors.New("data is too short to contain message ID in FIRST_FRAGMENT")
} else {
now = append(current, data[message_id_index:message_id_index+4]...)
}
}
} else if di_type == FOLLOW_ON_FRAGMENT {
if len(data) < 5 {
err = errors.New("data is too short to contain message ID in FOLLOW_ON_FRAGMENT")
} else {
now = append(current, data[1:5]...)
}
}
return
}
func maybeAppendExtendedOptions(di_flag DeliveryInstructions, data, current []byte) (now []byte, err error) {
if index, err := DeliveryInstructions(data).extended_options_index(); err != nil {
extended_options_length := common.Integer([]byte{data[index]})
now = append(current, data[index:index+extended_options_length]...)
}
return
}
func maybeAppendSize(di_flag DeliveryInstructions, di_type int, data, current []byte) (now []byte, err error) {
if di_type == FIRST_FRAGMENT {
if index, err := DeliveryInstructions(data).extended_options_index(); err != nil {
extended_options_length := common.Integer([]byte{data[index]})
now = append(current, data[index+extended_options_length:index+extended_options_length+2]...)
}
} else if di_type == FOLLOW_ON_FRAGMENT {
if len(data) < 7 {
err = errors.New("data is too short to contain size data")
} else {
now = append(now, data[5:7]...)
}
}
return
}
func readDeliveryInstructions(data []byte) (instructions DeliveryInstructions, remainder []byte, err error) {
if len(data) < 1 {
err = errors.New("no data provided")
return
}
di_flag := DeliveryInstructions(data[:1])
di_type, _ := di_flag.Type()
di_data := make([]byte, 0)
di_data = append(di_data, data[0])
if di_type == FIRST_FRAGMENT {
di_data, err = maybeAppendTunnelID(data, di_data)
if err != nil {
return
}
di_data, err = maybeAppendHash(di_flag, data, di_data)
if err != nil {
return
}
di_data, err = maybeAppendDelay(di_flag, data, di_data)
if err != nil {
return
}
di_data, err = maybeAppendMessageID(di_flag, di_type, data, di_data)
if err != nil {
return
}
di_data, err = maybeAppendExtendedOptions(di_flag, data, di_data)
if err != nil {
return
}
di_data, err = maybeAppendSize(di_flag, di_type, data, di_data)
if err != nil {
return
}
} else if di_type == FOLLOW_ON_FRAGMENT {
di_data, err = maybeAppendMessageID(di_flag, di_type, data, di_data)
if err != nil {
return
}
di_data, err = maybeAppendSize(di_flag, di_type, data, di_data)
if err != nil {
return
}
}
remainder = data[len(di_data):]
return
}