Files
core/protocol/Message Encoding Transfer.go

137 lines
5.1 KiB
Go

/*
File Username: Message Encoding Transfer.go
Copyright: 2021 Peernet s.r.o.
Author: Peter Kleissner
Transfer message encoding:
Offset Size Info
0 1 Control
1 1 Transfer Protocol
2 32 File Hash
Control = 0: Request Start
34 8 Offset to start reading in the file
42 8 Limit of bytes to read at the offset
50 16 Transfer ID. This will identify lite packets.
Offset + limit must not exceed the file size. Actual data transfer should be sent via lite packets.
The regular Peernet packets would be too CPU expensive and slow due to public key signing.
*/
package protocol
import (
"encoding/binary"
"errors"
"github.com/PeernetOfficial/core/btcec"
"github.com/google/uuid"
)
// MessageTransfer is the decoded transfer message.
// It is sent to initiate a file transfer, and to send data as part of a file transfer. The actual file data is encapsulated via UDT.
type MessageTransfer struct {
*MessageRaw // Underlying raw message.
Control uint8 // Control. See TransferControlX.
TransferProtocol uint8 // Embedded transfer protocol: 0 = UDT
Hash []byte // Hash of the file to transfer.
Offset uint64 // Offset to start reading at. Only TransferControlRequestStart.
Limit uint64 // Limit (count of bytes) to read starting at the offset. Only TransferControlRequestStart.
TransferID uuid.UUID // Transfer ID to identify lite packets.
Data []byte // Embedded protocol data. Only TransferControlActive.
}
const (
TransferControlRequestStart = 0 // Request start transfer of file. Data at byte 34 is offset and limit to read, each 8 bytes. Limit may be 0 to indicate entire file.
TransferControlNotAvailable = 1 // Requested file not available
TransferControlActive = 2 // Active file transfer
TransferControlTerminate = 3 // Terminate
)
const (
TransferProtocolUDT = 0 // UDT via lite packets. No encryption.
)
const transferPayloadHeaderSize = 34
// DecodeTransfer decodes a transfer message
func DecodeTransfer(msg *MessageRaw) (result *MessageTransfer, err error) {
if len(msg.Payload) < transferPayloadHeaderSize {
return nil, errors.New("transfer: invalid minimum length")
}
result = &MessageTransfer{
MessageRaw: msg,
Hash: make([]byte, HashSize),
}
result.Control = msg.Payload[0]
result.TransferProtocol = msg.Payload[1]
copy(result.Hash, msg.Payload[2:2+HashSize])
switch result.Control {
case TransferControlRequestStart:
// Offset and Limit must be provided after the header.
if len(msg.Payload) < transferPayloadHeaderSize+16 {
return nil, errors.New("transfer: invalid minimum length")
}
result.Offset = binary.LittleEndian.Uint64(msg.Payload[34 : 34+8])
result.Limit = binary.LittleEndian.Uint64(msg.Payload[42 : 42+8])
copy(result.TransferID[:], msg.Payload[50:50+16])
case TransferControlActive:
// Data should be transferred via lite packets for performance reasons, but it is allowed to be encapsulated in Peernet packets.
result.Data = msg.Payload[transferPayloadHeaderSize:]
}
return result, nil
}
// TransferMaxEmbedSize is a recommended default upper size of embedded data inside the Transfer message, to be used as MaxPacketSize limit in the embedded protocol.
// This value is chosen as the lowest denominator of different environments (IPv4, IPv6, Ethernet, Internet) for safe transfer, not for highest performance.
// The caller may send bigger payloads but may risk that data packets are simply dropped and never arrive. A MTU negotiation or detection could pimp that.
const TransferMaxEmbedSize = internetSafeMTU - PacketLengthMin - transferPayloadHeaderSize
// Same as TransferMaxEmbedSize but for encoding via lite packets.
const TransferMaxEmbedSizeLite = internetSafeMTU - PacketLiteSizeMin
// EncodeTransfer encodes a transfer message. The embedded packet size must be smaller than TransferMaxEmbedSize.
func EncodeTransfer(senderPrivateKey *btcec.PrivateKey, data []byte, control, transferProtocol uint8, hash []byte, offset, limit uint64, transferID uuid.UUID) (packetRaw []byte, err error) {
if control == TransferControlRequestStart && len(data) != 0 {
return nil, errors.New("transfer encode: payload not allowed in start")
} else if isPacketSizeExceed(transferPayloadHeaderSize, len(data)) {
return nil, errors.New("transfer encode: embedded packet too big")
}
packetSize := transferPayloadHeaderSize
if control == TransferControlRequestStart {
packetSize += 32
} else if control == TransferControlActive {
packetSize += len(data)
}
raw := make([]byte, packetSize)
raw[0] = control
raw[1] = transferProtocol
copy(raw[2:2+HashSize], hash)
if control == TransferControlRequestStart {
binary.LittleEndian.PutUint64(raw[34:34+8], offset)
binary.LittleEndian.PutUint64(raw[42:42+8], limit)
copy(raw[50:50+16], transferID[:])
} else if control == TransferControlActive {
copy(raw[34:34+len(data)], data)
}
return raw, nil
}
// IsLast checks if the incoming message is the last one in this transfer.
func (msg *MessageTransfer) IsLast() bool {
return msg.Control == TransferControlTerminate || msg.Control == TransferControlNotAvailable
}