/* File Name: Message Encoding Get Block.go Copyright: 2021 Peernet s.r.o. Author: Peter Kleissner Get Block message encoding: Offset Size Info 0 1 Control 1 33 Peer ID compressed form identifying which blockchain to transfer Control = 0: Request Blocks 34 8 Limit total count of blocks to transfer. The transfer will be terminated if the limit is reached. 42 8 Limit of bytes per block to transfer max. Blocks exceeding this limit will not be transferred. 50 16 Transfer ID. This will identify lite packets. 66 2 Count of block ranges 68 16 * ? List of block ranges Block range: 0 8 Block number 8 8 Count of blocks Control = 3: Active 34 ? Embedded block data as stream. For the block stream there is a header preceding each block: Offset Size Info 0 1 Availability 0 = Block range is available. 1 = Block range not available. 2 = Block range exceeds size limit. 1 16 Block range 17 8 Block size The limit in block range must be 1 if a block is returned. */ package protocol import ( "encoding/binary" "errors" "io" "github.com/PeernetOfficial/core/btcec" "github.com/google/uuid" ) const ( GetBlockControlRequestStart = 0 // Request start transfer of blocks GetBlockControlNotAvailable = 1 // Requested blockchain not available (not found) GetBlockControlActive = 2 // Active block transfer GetBlockControlTerminate = 3 // Terminate GetBlockControlEmpty = 4 // Requested blockchain has 0 blocks ) const ( GetBlockStatusAvailable = 0 GetBlockStatusNotAvailable = 1 GetBlockStatusSizeExceed = 2 ) // Min size of header for Get Block control 0 message. const getBlockRequestHeaderSize = 68 // MessageGetBlock is the decoded Get Block message. type MessageGetBlock struct { *MessageRaw // Underlying raw message. Control uint8 // Control. See TransferControlX. BlockchainPublicKey *btcec.PublicKey // Peer ID of blockchain to transfer. // fields valid only for GetBlockControlRequestStart TransferID uuid.UUID // Transfer ID to identify lite packets. LimitBlockCount uint64 // Limit total count of blocks to transfer MaxBlockSize uint64 // Limit of bytes per block to transfer max. Blocks exceeding this limit will not be transferred. TargetBlocks []BlockRange // Target list of block ranges to transfer. // fields valid only for GetBlockControlActive Data []byte // Embedded protocol data. } // BlockRange is a single start-count range. type BlockRange struct { Offset uint64 // Block number start Limit uint64 // Count of blocks } // DecodeGetBlock decodes a Get Block message func DecodeGetBlock(msg *MessageRaw) (result *MessageGetBlock, err error) { if len(msg.Payload) < 34 { return nil, errors.New("get block: invalid minimum length") } result = &MessageGetBlock{ MessageRaw: msg, } result.Control = msg.Payload[0] peerIDcompressed := msg.Payload[1:34] if result.BlockchainPublicKey, err = btcec.ParsePubKey(peerIDcompressed, btcec.S256()); err != nil { return nil, err } if result.Control == GetBlockControlRequestStart { if len(msg.Payload) < getBlockRequestHeaderSize { return nil, errors.New("get block: invalid minimum length") } result.LimitBlockCount = binary.LittleEndian.Uint64(msg.Payload[34 : 34+8]) result.MaxBlockSize = binary.LittleEndian.Uint64(msg.Payload[42 : 42+8]) copy(result.TransferID[:], msg.Payload[50:50+16]) countBlockRanges := int(binary.LittleEndian.Uint16(msg.Payload[66 : 66+2])) if countBlockRanges == 0 { return nil, errors.New("get block: empty block range") } else if len(msg.Payload) < getBlockRequestHeaderSize+16*countBlockRanges { return nil, errors.New("get block: cound block ranges exceeds length") } index := getBlockRequestHeaderSize for n := 0; n < countBlockRanges; n++ { var target BlockRange target.Offset = binary.LittleEndian.Uint64(msg.Payload[index : index+8]) target.Limit = binary.LittleEndian.Uint64(msg.Payload[index+8 : index+16]) result.TargetBlocks = append(result.TargetBlocks, target) index += 16 } } else if result.Control == GetBlockControlActive { result.Data = msg.Payload[34:] } return result, nil } // EncodeGetBlock encodes a Get Block message. The embedded packet size must be smaller than TransferMaxEmbedSize. func EncodeGetBlock(senderPrivateKey *btcec.PrivateKey, data []byte, control uint8, blockchainPublicKey *btcec.PublicKey, limitBlockCount, maxBlockSize uint64, targetBlocks []BlockRange, transferID uuid.UUID) (packetRaw []byte, err error) { if control == GetBlockControlRequestStart && len(data) != 0 { return nil, errors.New("get block encode: payload not allowed in start") } else if isPacketSizeExceed(transferPayloadHeaderSize, len(data)) { return nil, errors.New("get block encode: embedded packet too big") } else if control == GetBlockControlRequestStart && isPacketSizeExceed(getBlockRequestHeaderSize, len(targetBlocks)*16) { return nil, errors.New("get block encode: too many target block ranges") } packetSize := transferPayloadHeaderSize if control == GetBlockControlRequestStart { packetSize = getBlockRequestHeaderSize + len(targetBlocks)*16 } else if control == GetBlockControlActive { packetSize += len(data) } raw := make([]byte, packetSize) raw[0] = control targetPeerID := blockchainPublicKey.SerializeCompressed() copy(raw[1:34], targetPeerID) if control == GetBlockControlRequestStart { binary.LittleEndian.PutUint64(raw[34:34+8], limitBlockCount) binary.LittleEndian.PutUint64(raw[42:42+8], maxBlockSize) copy(raw[50:50+16], transferID[:]) binary.LittleEndian.PutUint16(raw[66:66+2], uint16(len(targetBlocks))) index := getBlockRequestHeaderSize for _, target := range targetBlocks { binary.LittleEndian.PutUint64(raw[index:index+8], target.Offset) binary.LittleEndian.PutUint64(raw[index+8:index+16], target.Limit) index += 16 } } else if control == GetBlockControlActive { 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 *MessageGetBlock) IsLast() bool { return msg.Control == GetBlockControlTerminate || msg.Control == GetBlockControlNotAvailable || msg.Control == GetBlockControlEmpty } // BlockTransferWriteHeader starts writing the header for a block transfer. func BlockTransferWriteHeader(writer io.Writer, availability uint8, targetBlock BlockRange, blockSize uint64) (err error) { header := make([]byte, 25) header[0] = availability binary.LittleEndian.PutUint64(header[1:9], targetBlock.Offset) binary.LittleEndian.PutUint64(header[9:17], targetBlock.Limit) binary.LittleEndian.PutUint64(header[17:25], blockSize) _, err = writer.Write(header) return err } // BlockTransferReadBlock reads the header and the block from the reader func BlockTransferReadBlock(reader io.Reader, maxBlockSize uint64) (data []byte, targetBlock BlockRange, blockSize uint64, availability uint8, err error) { header := make([]byte, 25) if _, err := io.ReadAtLeast(reader, header, len(header)); err != nil { return nil, targetBlock, 0, 0, err } availability = header[0] targetBlock.Offset = binary.LittleEndian.Uint64(header[1:9]) targetBlock.Limit = binary.LittleEndian.Uint64(header[9:17]) blockSize = binary.LittleEndian.Uint64(header[17:25]) if targetBlock.Limit == 0 { return nil, targetBlock, blockSize, availability, errors.New("empty target block limit") } else if availability != GetBlockStatusAvailable { // return if status indicates the block is not available return nil, targetBlock, blockSize, availability, nil } if blockSize > maxBlockSize { return nil, targetBlock, blockSize, availability, errors.New("remote block size exceeds limit") } else if targetBlock.Limit != 1 { return nil, targetBlock, blockSize, availability, errors.New("invalid target block limit") } // read the block block := make([]byte, blockSize) if _, err := io.ReadAtLeast(reader, block, len(block)); err != nil { return nil, targetBlock, blockSize, availability, err } return block, targetBlock, blockSize, availability, nil }