/* File Name: 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 34 ? Embedded protocol data */ package protocol import ( "encoding/binary" "errors" "github.com/PeernetOfficial/core/btcec" ) // 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. 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 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]) case TransferControlActive: result.Data = msg.Payload[34:] } 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 // 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) (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 += 16 } 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) } 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 }