mirror of
https://github.com/PeernetOfficial/Abstraction.git
synced 2026-07-22 05:07:50 +01:00
added example package
This commit is contained in:
27
vendor/github.com/PeernetOfficial/core/protocol/Command.go
generated
vendored
Normal file
27
vendor/github.com/PeernetOfficial/core/protocol/Command.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
File Name: Command.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package protocol
|
||||
|
||||
// Commands between peers
|
||||
const (
|
||||
// Peer List Management
|
||||
CommandAnnouncement = 0 // Announcement
|
||||
CommandResponse = 1 // Response
|
||||
CommandPing = 2 // Keep-alive message (no payload).
|
||||
CommandPong = 3 // Response to ping (no payload).
|
||||
CommandLocalDiscovery = 4 // Local discovery
|
||||
CommandTraverse = 5 // Help establish a connection between 2 remote peers
|
||||
|
||||
// Blockchain
|
||||
CommandGetBlock = 6 // Request blocks for specified peer.
|
||||
|
||||
// File Discovery
|
||||
CommandTransfer = 8 // File transfer.
|
||||
|
||||
// Debug
|
||||
CommandChat = 10 // Chat message [debug]
|
||||
)
|
||||
49
vendor/github.com/PeernetOfficial/core/protocol/File Transfer.go
generated
vendored
Normal file
49
vendor/github.com/PeernetOfficial/core/protocol/File Transfer.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
File Name: File Transfer.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
Encoding of file transfer protocol. Each transfer starts with a header:
|
||||
Offset Size Info
|
||||
0 8 Total File Size
|
||||
8 8 Transfer Size
|
||||
*/
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// FileTransferWriteHeader starts writing the header for a file transfer.
|
||||
func FileTransferWriteHeader(writer io.Writer, fileSize, transferSize uint64) (err error) {
|
||||
// Send the header: Total File Size and Transfer Size.
|
||||
header := make([]byte, 16)
|
||||
binary.LittleEndian.PutUint64(header[0:8], fileSize)
|
||||
binary.LittleEndian.PutUint64(header[8:16], transferSize)
|
||||
if n, err := writer.Write(header); err != nil {
|
||||
return err
|
||||
} else if n != len(header) {
|
||||
return errors.New("error sending header")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FileTransferReadHeader starts reading the header for a file transfer. It will only read the header and keeps the connection open.
|
||||
func FileTransferReadHeader(reader io.Reader) (fileSize, transferSize uint64, err error) {
|
||||
// read the header
|
||||
header := make([]byte, 16)
|
||||
if n, err := reader.Read(header); err != nil {
|
||||
return 0, 0, err
|
||||
} else if n != len(header) {
|
||||
return 0, 0, errors.New("error reading header")
|
||||
}
|
||||
|
||||
fileSize = binary.LittleEndian.Uint64(header[0:8])
|
||||
transferSize = binary.LittleEndian.Uint64(header[8:16])
|
||||
|
||||
return fileSize, transferSize, nil
|
||||
}
|
||||
27
vendor/github.com/PeernetOfficial/core/protocol/Hash.go
generated
vendored
Normal file
27
vendor/github.com/PeernetOfficial/core/protocol/Hash.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
File Name: Hash.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
"lukechampine.com/blake3"
|
||||
)
|
||||
|
||||
// HashData abstracts the hash function.
|
||||
func HashData(data []byte) (hash []byte) {
|
||||
hash32 := blake3.Sum256(data)
|
||||
return hash32[:]
|
||||
}
|
||||
|
||||
// HashSize is blake3 hash digest size = 256 bits
|
||||
const HashSize = 32
|
||||
|
||||
// PublicKey2NodeID translates the Public Key into the node ID used in the Kademlia network.
|
||||
// It is also referenced in various other places including blockchain data at runtime. The node ID identifies the owner.
|
||||
func PublicKey2NodeID(publicKey *btcec.PublicKey) (nodeID []byte) {
|
||||
return HashData(publicKey.SerializeCompressed())
|
||||
}
|
||||
310
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding Announcement.go
generated
vendored
Normal file
310
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding Announcement.go
generated
vendored
Normal file
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
File Name: Message Encoding Announcement.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// MessageAnnouncement is the decoded announcement message.
|
||||
type MessageAnnouncement struct {
|
||||
*MessageRaw // Underlying raw message
|
||||
Protocol uint8 // Protocol version supported (low 4 bits).
|
||||
Features uint8 // Feature support
|
||||
Actions uint8 // Action bit array. See ActionX
|
||||
BlockchainHeight uint64 // Blockchain height
|
||||
BlockchainVersion uint64 // Blockchain version
|
||||
PortInternal uint16 // Internal port. Can be used to detect NATs.
|
||||
PortExternal uint16 // External port if known. 0 if not. Can be used for UPnP support.
|
||||
UserAgent string // User Agent. Format "Software/Version". Required in the initial announcement/bootstrap. UTF-8 encoded. Max length is 255 bytes.
|
||||
FindPeerKeys []KeyHash // FIND_PEER data
|
||||
FindDataKeys []KeyHash // FIND_VALUE data
|
||||
InfoStoreFiles []InfoStore // INFO_STORE data
|
||||
}
|
||||
|
||||
// KeyHash is a single blake3 key hash
|
||||
type KeyHash struct {
|
||||
Hash []byte
|
||||
}
|
||||
|
||||
// InfoStore informs about files stored
|
||||
type InfoStore struct {
|
||||
ID KeyHash // Hash of the file
|
||||
Size uint64 // Size of the file
|
||||
Type uint8 // Type of the file: 0 = File, 1 = Header file containing list of parts
|
||||
}
|
||||
|
||||
// Features are sent as bit array in the Announcement message.
|
||||
const (
|
||||
FeatureIPv4Listen = 0 // Sender listens on IPv4
|
||||
FeatureIPv6Listen = 1 // Sender listens on IPv6
|
||||
FeatureFirewall = 2 // Sender indicates a potential firewall. This informs uncontacted peers that a Traverse message might be required to establish a connection.
|
||||
)
|
||||
|
||||
// Actions between peers, sent via Announcement message. They correspond to the bit array index.
|
||||
const (
|
||||
ActionFindSelf = 0 // FIND_SELF Request closest neighbors to self
|
||||
ActionFindPeer = 1 // FIND_PEER Request closest neighbors to target peer
|
||||
ActionFindValue = 2 // FIND_VALUE Request data or closest peers
|
||||
ActionInfoStore = 3 // INFO_STORE Sender indicates storing provided data
|
||||
)
|
||||
|
||||
// Minimum length of Announcement payload header without User Agent
|
||||
const announcementPayloadHeaderSize = 24
|
||||
|
||||
// DecodeAnnouncement decodes the incoming announcement message. Returns nil if invalid.
|
||||
func DecodeAnnouncement(msg *MessageRaw) (result *MessageAnnouncement, err error) {
|
||||
result = &MessageAnnouncement{
|
||||
MessageRaw: msg,
|
||||
}
|
||||
|
||||
if len(msg.Payload) < announcementPayloadHeaderSize {
|
||||
return nil, errors.New("announcement: invalid minimum length")
|
||||
}
|
||||
|
||||
result.Protocol = msg.Payload[0] & 0x0F // Protocol version support is stored in the first 4 bits
|
||||
result.Features = msg.Payload[1] // Feature support
|
||||
result.Actions = msg.Payload[2]
|
||||
result.BlockchainHeight = binary.LittleEndian.Uint64(msg.Payload[3 : 3+8])
|
||||
result.BlockchainVersion = binary.LittleEndian.Uint64(msg.Payload[11 : 11+8])
|
||||
result.PortInternal = binary.LittleEndian.Uint16(msg.Payload[19 : 19+2])
|
||||
result.PortExternal = binary.LittleEndian.Uint16(msg.Payload[21 : 21+2])
|
||||
|
||||
userAgentLength := int(msg.Payload[23])
|
||||
if userAgentLength > 0 {
|
||||
if userAgentLength > len(msg.Payload)-announcementPayloadHeaderSize {
|
||||
return nil, errors.New("announcement: user agent overflow")
|
||||
}
|
||||
|
||||
userAgentB := msg.Payload[announcementPayloadHeaderSize : announcementPayloadHeaderSize+userAgentLength]
|
||||
if !utf8.Valid(userAgentB) {
|
||||
return nil, errors.New("announcement: user agent invalid encoding")
|
||||
}
|
||||
|
||||
result.UserAgent = string(userAgentB)
|
||||
}
|
||||
|
||||
data := msg.Payload[announcementPayloadHeaderSize+userAgentLength:]
|
||||
|
||||
// FIND_PEER
|
||||
if result.Actions&(1<<ActionFindPeer) > 0 {
|
||||
keys, read, valid := decodeKeys(data)
|
||||
if !valid {
|
||||
return nil, errors.New("announcement: FIND_PEER invalid data")
|
||||
}
|
||||
|
||||
data = data[read:]
|
||||
result.FindPeerKeys = keys
|
||||
}
|
||||
|
||||
// FIND_VALUE
|
||||
if result.Actions&(1<<ActionFindValue) > 0 {
|
||||
keys, read, valid := decodeKeys(data)
|
||||
if !valid {
|
||||
return nil, errors.New("announcement: FIND_VALUE invalid data")
|
||||
}
|
||||
|
||||
data = data[read:]
|
||||
result.FindDataKeys = keys
|
||||
}
|
||||
|
||||
// INFO_STORE
|
||||
if result.Actions&(1<<ActionInfoStore) > 0 {
|
||||
files, _, valid := decodeInfoStore(data)
|
||||
if !valid {
|
||||
return nil, errors.New("announcement: INFO_STORE invalid data")
|
||||
}
|
||||
|
||||
// commented out because never used
|
||||
//data = data[read:]
|
||||
result.InfoStoreFiles = files
|
||||
}
|
||||
|
||||
// Accept extra data in case future features append additional data
|
||||
//if len(data) > 0 {
|
||||
// return nil, errors.New("announcement: Unexpected extra data")
|
||||
//}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// decodeKeys decodes keys. Header is 2 bytes (count) followed by the actual keys (each 32 bytes blake3 hash).
|
||||
func decodeKeys(data []byte) (keys []KeyHash, read int, valid bool) {
|
||||
if len(data) < 2+HashSize { // minimum length
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
count := binary.LittleEndian.Uint16(data[0:2])
|
||||
|
||||
if read = 2 + int(count)*HashSize; len(data) < read {
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
for n := 0; n < int(count); n++ {
|
||||
key := make([]byte, HashSize)
|
||||
copy(key, data[2+n*HashSize:2+n*HashSize+HashSize])
|
||||
keys = append(keys, KeyHash{Hash: key})
|
||||
}
|
||||
|
||||
return keys, read, true
|
||||
}
|
||||
|
||||
func decodeInfoStore(data []byte) (files []InfoStore, read int, valid bool) {
|
||||
if len(data) < 2+41 { // minimum length
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
count := binary.LittleEndian.Uint16(data[0:2])
|
||||
|
||||
if read = 2 + int(count)*41; len(data) < read {
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
for n := 0; n < int(count); n++ {
|
||||
file := InfoStore{}
|
||||
file.ID.Hash = make([]byte, HashSize)
|
||||
copy(file.ID.Hash, data[2+n*41:2+n*41+HashSize])
|
||||
file.Size = binary.LittleEndian.Uint64(data[2+n*41+32 : 2+n*41+32+8])
|
||||
file.Type = data[2+n*41+40]
|
||||
|
||||
files = append(files, file)
|
||||
}
|
||||
|
||||
return files, read, true
|
||||
}
|
||||
|
||||
// EncodeAnnouncement encodes an announcement message. It may return multiple messages if the input does not fit into one.
|
||||
// findPeer is a list of node IDs (blake3 hash of peer ID compressed form)
|
||||
// findValue is a list of hashes
|
||||
// files is a list of files stored to inform about
|
||||
func EncodeAnnouncement(sendUA, findSelf bool, findPeer []KeyHash, findValue []KeyHash, files []InfoStore, features byte, blockchainHeight, blockchainVersion uint64, userAgent string) (packetsRaw [][]byte) {
|
||||
createPacketLoop:
|
||||
for {
|
||||
raw := make([]byte, 64*1024) // max UDP packet size
|
||||
packetSize := announcementPayloadHeaderSize
|
||||
|
||||
raw[0] = byte(ProtocolVersion) // Protocol
|
||||
raw[1] = features // Feature support
|
||||
//raw[2] = Actions // Action bit array
|
||||
|
||||
binary.LittleEndian.PutUint64(raw[3:3+8], blockchainHeight)
|
||||
binary.LittleEndian.PutUint64(raw[11:11+8], blockchainVersion)
|
||||
|
||||
// only on initial announcement the User Agent must be provided according to the protocol spec
|
||||
if sendUA {
|
||||
userAgentB := []byte(userAgent)
|
||||
if len(userAgentB) > 255 {
|
||||
userAgentB = userAgentB[:255]
|
||||
}
|
||||
|
||||
raw[23] = byte(len(userAgentB))
|
||||
copy(raw[announcementPayloadHeaderSize:announcementPayloadHeaderSize+len(userAgentB)], userAgentB)
|
||||
packetSize += len(userAgentB)
|
||||
}
|
||||
|
||||
// FIND_SELF
|
||||
if findSelf {
|
||||
raw[2] |= 1 << ActionFindSelf
|
||||
}
|
||||
|
||||
// FIND_PEER
|
||||
if len(findPeer) > 0 {
|
||||
// check if there is enough space for at least the header and 1 record
|
||||
if isPacketSizeExceed(packetSize, 2+32) {
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
raw[2] |= 1 << ActionFindPeer
|
||||
index := packetSize
|
||||
packetSize += 2
|
||||
|
||||
for n, find := range findPeer {
|
||||
// check if minimum length is available in packet
|
||||
if isPacketSizeExceed(packetSize, 32) {
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
findPeer = findPeer[n:]
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint16(raw[index:index+2], uint16(n+1))
|
||||
copy(raw[index+2+32*n:index+2+32*n+32], find.Hash)
|
||||
packetSize += 32
|
||||
}
|
||||
|
||||
findPeer = nil
|
||||
}
|
||||
|
||||
// FIND_VALUE
|
||||
if len(findValue) > 0 {
|
||||
// check if there is enough space for at least the header and 1 record
|
||||
if isPacketSizeExceed(packetSize, 2+32) {
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
raw[2] |= 1 << ActionFindValue
|
||||
index := packetSize
|
||||
packetSize += 2
|
||||
|
||||
for n, find := range findValue {
|
||||
// check if minimum length is available in packet
|
||||
if isPacketSizeExceed(packetSize, 32) {
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
findValue = findValue[n:]
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint16(raw[index:index+2], uint16(n+1))
|
||||
copy(raw[index+2+32*n:index+2+32*n+32], find.Hash)
|
||||
packetSize += 32
|
||||
}
|
||||
|
||||
findValue = nil
|
||||
}
|
||||
|
||||
// INFO_STORE
|
||||
if len(files) > 0 {
|
||||
// check if there is enough space for at least the header and 1 record
|
||||
if isPacketSizeExceed(packetSize, 2+41) {
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
raw[2] |= 1 << ActionInfoStore
|
||||
index := packetSize
|
||||
packetSize += 2
|
||||
|
||||
for n, file := range files {
|
||||
// check if minimum length is available in packet
|
||||
if isPacketSizeExceed(packetSize, 41) {
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
files = files[n:]
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint16(raw[index:index+2], uint16(n+1))
|
||||
copy(raw[index+2+41*n:index+2+41*n+32], file.ID.Hash)
|
||||
|
||||
binary.LittleEndian.PutUint64(raw[index+2+41*n+32:index+2+41*n+32+8], file.Size)
|
||||
raw[index+2+41*n+40] = file.Type
|
||||
|
||||
packetSize += 41
|
||||
}
|
||||
|
||||
files = nil
|
||||
}
|
||||
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
|
||||
if len(findPeer) == 0 && len(findValue) == 0 && len(files) == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
230
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding Get Block.go
generated
vendored
Normal file
230
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding Get Block.go
generated
vendored
Normal file
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
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
|
||||
}
|
||||
445
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding Response.go
generated
vendored
Normal file
445
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding Response.go
generated
vendored
Normal file
@@ -0,0 +1,445 @@
|
||||
/*
|
||||
File Name: Message Encoding Response.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
)
|
||||
|
||||
// MessageResponse is the decoded response message.
|
||||
type MessageResponse struct {
|
||||
*MessageRaw // Underlying raw message
|
||||
Protocol uint8 // Protocol version supported (low 4 bits).
|
||||
Features uint8 // Feature support (high 4 bits). Future use.
|
||||
Actions uint8 // Action bit array. See ActionX
|
||||
BlockchainHeight uint64 // Blockchain height
|
||||
BlockchainVersion uint64 // Blockchain version
|
||||
PortInternal uint16 // Internal port. Can be used to detect NATs.
|
||||
PortExternal uint16 // External port if known. 0 if not. Can be used for UPnP support.
|
||||
UserAgent string // User Agent. Format "Software/Version". Required in the initial announcement/bootstrap. UTF-8 encoded. Max length is 255 bytes.
|
||||
Hash2Peers []Hash2Peer // List of peers that know the requested hashes or at least are close to it
|
||||
FilesEmbed []EmbeddedFileData // Files that were embedded in the response
|
||||
HashesNotFound [][]byte // Hashes that were reported back as not found
|
||||
}
|
||||
|
||||
// PeerRecord informs about a peer
|
||||
type PeerRecord struct {
|
||||
PublicKey *btcec.PublicKey // Public Key
|
||||
NodeID []byte // Kademlia Node ID
|
||||
IPv4 net.IP // IPv4 address. 0 if not set.
|
||||
IPv4Port uint16 // Port (actual one used for connection)
|
||||
IPv4PortReportedInternal uint16 // Internal port as reported by that peer. This can be used to identify whether the peer is potentially behind a NAT.
|
||||
IPv4PortReportedExternal uint16 // External port as reported by that peer. This is used in case of port forwarding (manual or automated).
|
||||
IPv6 net.IP // IPv6 address. 0 if not set.
|
||||
IPv6Port uint16 // Port (actual one used for connection)
|
||||
IPv6PortReportedInternal uint16 // Internal port as reported by that peer. This can be used to identify whether the peer is potentially behind a NAT.
|
||||
IPv6PortReportedExternal uint16 // External port as reported by that peer. This is used in case of port forwarding (manual or automated).
|
||||
LastContact uint32 // Last contact in seconds
|
||||
LastContactT time.Time // Last contact time translated from seconds
|
||||
Features uint8 // Feature support. Same as in Announcement/Response message.
|
||||
}
|
||||
|
||||
// Hash2Peer links a hash to peers who are known to store the data and to peers who are considered close to the hash
|
||||
type Hash2Peer struct {
|
||||
ID KeyHash // Hash that was queried
|
||||
Closest []PeerRecord // Closest peers
|
||||
Storing []PeerRecord // Peers known to store the data identified by the hash
|
||||
IsLast bool // Whether it is the last records returned for the requested hash and no more results will follow
|
||||
}
|
||||
|
||||
// EmbeddedFileData contains embedded data sent within a response
|
||||
type EmbeddedFileData struct {
|
||||
ID KeyHash // Hash of the file
|
||||
Data []byte // Data
|
||||
}
|
||||
|
||||
// Actions in Response message
|
||||
const (
|
||||
ActionSequenceLast = 0 // SEQUENCE_LAST Last response to the announcement in the sequence
|
||||
)
|
||||
|
||||
// DecodeResponse decodes the incoming response message. Returns nil if invalid.
|
||||
func DecodeResponse(msg *MessageRaw) (result *MessageResponse, err error) {
|
||||
result = &MessageResponse{
|
||||
MessageRaw: msg,
|
||||
}
|
||||
|
||||
if len(msg.Payload) < announcementPayloadHeaderSize+6 {
|
||||
return nil, errors.New("response: invalid minimum length")
|
||||
}
|
||||
|
||||
result.Protocol = msg.Payload[0] & 0x0F // Protocol version support is stored in the first 4 bits
|
||||
result.Features = msg.Payload[1] // Feature support
|
||||
result.Actions = msg.Payload[2]
|
||||
result.BlockchainHeight = binary.LittleEndian.Uint64(msg.Payload[3 : 3+8])
|
||||
result.BlockchainVersion = binary.LittleEndian.Uint64(msg.Payload[11 : 11+8])
|
||||
result.PortInternal = binary.LittleEndian.Uint16(msg.Payload[19 : 19+2])
|
||||
result.PortExternal = binary.LittleEndian.Uint16(msg.Payload[21 : 21+2])
|
||||
|
||||
userAgentLength := int(msg.Payload[23])
|
||||
read := announcementPayloadHeaderSize
|
||||
|
||||
if userAgentLength > 0 {
|
||||
if userAgentLength > len(msg.Payload)-announcementPayloadHeaderSize {
|
||||
return nil, errors.New("response: user agent overflow")
|
||||
}
|
||||
|
||||
userAgentB := msg.Payload[announcementPayloadHeaderSize : announcementPayloadHeaderSize+userAgentLength]
|
||||
if !utf8.Valid(userAgentB) {
|
||||
return nil, errors.New("response: user agent invalid encoding")
|
||||
}
|
||||
|
||||
result.UserAgent = string(userAgentB)
|
||||
read += userAgentLength
|
||||
}
|
||||
|
||||
countPeerResponses := binary.LittleEndian.Uint16(msg.Payload[read+0 : read+0+2])
|
||||
countEmbeddedFiles := binary.LittleEndian.Uint16(msg.Payload[read+2 : read+2+2])
|
||||
countHashesNotFound := binary.LittleEndian.Uint16(msg.Payload[read+4 : read+4+2])
|
||||
read += 6
|
||||
|
||||
if countPeerResponses == 0 && countEmbeddedFiles == 0 && countHashesNotFound == 0 {
|
||||
// Empty responses are allowed. They can be useful as quasi-pings to get the latest blockchain info of the peer.
|
||||
return
|
||||
}
|
||||
|
||||
data := msg.Payload[read:]
|
||||
|
||||
// Peer response data
|
||||
if countPeerResponses > 0 {
|
||||
hash2Peers, read, valid := decodePeerRecord(data, int(countPeerResponses))
|
||||
if !valid {
|
||||
return nil, errors.New("response: peer info invalid data")
|
||||
}
|
||||
data = data[read:]
|
||||
|
||||
result.Hash2Peers = append(result.Hash2Peers, hash2Peers...)
|
||||
}
|
||||
|
||||
// Embedded files
|
||||
if countEmbeddedFiles > 0 {
|
||||
filesEmbed, read, valid := decodeEmbeddedFile(data, int(countEmbeddedFiles))
|
||||
if !valid {
|
||||
return nil, errors.New("response: embedded file invalid data")
|
||||
}
|
||||
data = data[read:]
|
||||
|
||||
result.FilesEmbed = append(result.FilesEmbed, filesEmbed...)
|
||||
}
|
||||
|
||||
// Hashes not found
|
||||
if countHashesNotFound > 0 {
|
||||
if len(data) < int(countHashesNotFound)*32 {
|
||||
return nil, errors.New("response: hash list invalid data")
|
||||
}
|
||||
|
||||
for n := 0; n < int(countHashesNotFound); n++ {
|
||||
hash := make([]byte, HashSize)
|
||||
copy(hash, data[n*32:n*32+32])
|
||||
|
||||
result.HashesNotFound = append(result.HashesNotFound, hash)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Length of peer record in bytes
|
||||
const peerRecordSize = 70
|
||||
|
||||
// decodePeerRecord decodes the response data for FIND_SELF, FIND_PEER and FIND_VALUE messages
|
||||
func decodePeerRecord(data []byte, count int) (hash2Peers []Hash2Peer, read int, valid bool) {
|
||||
index := 0
|
||||
|
||||
for n := 0; n < count; n++ {
|
||||
if read += 34; len(data) < read {
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
hash := make([]byte, HashSize)
|
||||
copy(hash, data[index:index+32])
|
||||
countField := binary.LittleEndian.Uint16(data[index+32:index+32+2]) & 0x7FFF
|
||||
isLast := binary.LittleEndian.Uint16(data[index+32:index+32+2])&0x8000 > 0
|
||||
index += 34
|
||||
|
||||
hash2Peer := Hash2Peer{ID: KeyHash{hash}, IsLast: isLast}
|
||||
|
||||
// Response contains peer records
|
||||
for m := 0; m < int(countField); m++ {
|
||||
if read += peerRecordSize; len(data) < read {
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
peer := PeerRecord{}
|
||||
|
||||
peerIDcompressed := make([]byte, 33)
|
||||
copy(peerIDcompressed[:], data[index:index+33])
|
||||
|
||||
// IPv4
|
||||
ipv4B := make([]byte, 4)
|
||||
copy(ipv4B[:], data[index+33:index+33+4])
|
||||
|
||||
peer.IPv4 = ipv4B
|
||||
peer.IPv4Port = binary.LittleEndian.Uint16(data[index+37 : index+37+2])
|
||||
peer.IPv4PortReportedInternal = binary.LittleEndian.Uint16(data[index+39 : index+39+2])
|
||||
peer.IPv4PortReportedExternal = binary.LittleEndian.Uint16(data[index+41 : index+41+2])
|
||||
|
||||
// IPv6
|
||||
ipv6B := make([]byte, 16)
|
||||
copy(ipv6B[:], data[index+43:index+43+16])
|
||||
|
||||
peer.IPv6 = ipv6B
|
||||
peer.IPv6Port = binary.LittleEndian.Uint16(data[index+59 : index+59+2])
|
||||
peer.IPv6PortReportedInternal = binary.LittleEndian.Uint16(data[index+61 : index+61+2])
|
||||
peer.IPv6PortReportedExternal = binary.LittleEndian.Uint16(data[index+63 : index+63+2])
|
||||
|
||||
if peer.IPv6.To4() != nil { // IPv6 address mismatch
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
peer.LastContact = binary.LittleEndian.Uint32(data[index+65 : index+65+4])
|
||||
peer.LastContactT = time.Now().Add(-time.Second * time.Duration(peer.LastContact))
|
||||
peer.Features = data[index+69] & 0x7F
|
||||
reason := data[index+69] >> 7
|
||||
|
||||
var err error
|
||||
if peer.PublicKey, err = btcec.ParsePubKey(peerIDcompressed, btcec.S256()); err != nil {
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
peer.NodeID = PublicKey2NodeID(peer.PublicKey)
|
||||
|
||||
if reason == 0 { // Peer was returned because it is close to the requested hash
|
||||
hash2Peer.Closest = append(hash2Peer.Closest, peer)
|
||||
} else if reason == 1 { // Peer stores the data
|
||||
hash2Peer.Storing = append(hash2Peer.Storing, peer)
|
||||
}
|
||||
|
||||
index += peerRecordSize
|
||||
}
|
||||
|
||||
hash2Peers = append(hash2Peers, hash2Peer)
|
||||
}
|
||||
|
||||
return hash2Peers, read, true
|
||||
}
|
||||
|
||||
// decodeEmbeddedFile decodes the embedded file response data for FIND_VALUE
|
||||
func decodeEmbeddedFile(data []byte, count int) (filesEmbed []EmbeddedFileData, read int, valid bool) {
|
||||
index := 0
|
||||
|
||||
for n := 0; n < count; n++ {
|
||||
if read += 34; len(data) < read {
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
hash := make([]byte, HashSize)
|
||||
copy(hash, data[index:index+32])
|
||||
sizeField := int(binary.LittleEndian.Uint16(data[index+32 : index+32+2]))
|
||||
index += 34
|
||||
|
||||
if read += sizeField; len(data) < read {
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
fileData := make([]byte, sizeField)
|
||||
copy(fileData[:], data[index:index+sizeField])
|
||||
|
||||
index += sizeField
|
||||
|
||||
// validate the hash
|
||||
if !bytes.Equal(hash, HashData(fileData)) {
|
||||
return nil, read, false
|
||||
}
|
||||
|
||||
filesEmbed = append(filesEmbed, EmbeddedFileData{ID: KeyHash{Hash: hash}, Data: fileData})
|
||||
}
|
||||
|
||||
return filesEmbed, read, true
|
||||
}
|
||||
|
||||
// EmbeddedFileSizeMax is the maximum size of embedded files in response messages. Any file exceeding that must be shared via regular file transfer.
|
||||
const EmbeddedFileSizeMax = udpMaxPacketSize - PacketLengthMin - announcementPayloadHeaderSize - 2 - 35
|
||||
|
||||
// EncodeResponse encodes a response message
|
||||
// hash2Peers will be modified.
|
||||
func EncodeResponse(sendUA bool, hash2Peers []Hash2Peer, filesEmbed []EmbeddedFileData, hashesNotFound [][]byte, features byte, blockchainHeight, blockchainVersion uint64, userAgent string) (packetsRaw [][]byte, err error) {
|
||||
for n := range filesEmbed {
|
||||
if len(filesEmbed[n].Data) > EmbeddedFileSizeMax {
|
||||
return nil, errors.New("embedded file too big")
|
||||
}
|
||||
}
|
||||
|
||||
createPacketLoop:
|
||||
for {
|
||||
raw := make([]byte, 64*1024) // max UDP packet size
|
||||
packetSize := announcementPayloadHeaderSize
|
||||
|
||||
raw[0] = byte(ProtocolVersion) // Protocol
|
||||
raw[1] = features // Feature support
|
||||
//raw[2] = Actions // Action bit array
|
||||
|
||||
binary.LittleEndian.PutUint64(raw[3:3+8], blockchainHeight)
|
||||
binary.LittleEndian.PutUint64(raw[11:11+8], blockchainVersion)
|
||||
|
||||
// only on initial response the User Agent must be provided according to the protocol spec
|
||||
if sendUA {
|
||||
userAgentB := []byte(userAgent)
|
||||
if len(userAgentB) > 255 {
|
||||
userAgentB = userAgentB[:255]
|
||||
}
|
||||
|
||||
raw[23] = byte(len(userAgentB))
|
||||
copy(raw[announcementPayloadHeaderSize:announcementPayloadHeaderSize+len(userAgentB)], userAgentB)
|
||||
packetSize += len(userAgentB)
|
||||
}
|
||||
|
||||
// 3 count field at raw[index]: count of peer responses, embedded files, and hashes not found
|
||||
countIndex := packetSize
|
||||
packetSize += 6
|
||||
|
||||
// Encode the peer response data for FIND_SELF, FIND_PEER and FIND_VALUE requests.
|
||||
if len(hash2Peers) > 0 {
|
||||
for n, hash2Peer := range hash2Peers {
|
||||
if isPacketSizeExceed(packetSize, 34+peerRecordSize) { // check if minimum length is available in packet
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
hash2Peers = hash2Peers[n:]
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
index := packetSize
|
||||
copy(raw[index:index+32], hash2Peer.ID.Hash)
|
||||
count2Index := index + 32
|
||||
|
||||
packetSize += 34
|
||||
count2 := uint16(0)
|
||||
|
||||
for m := range hash2Peer.Storing {
|
||||
if isPacketSizeExceed(packetSize, peerRecordSize) { // check if minimum length is available in packet
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
hash2Peers = hash2Peers[n:]
|
||||
hash2Peer.Storing = hash2Peer.Storing[m:]
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
index := packetSize
|
||||
encodePeerRecord(raw[index:index+peerRecordSize], &hash2Peer.Storing[m], 1)
|
||||
|
||||
packetSize += peerRecordSize
|
||||
binary.LittleEndian.PutUint16(raw[count2Index+0:count2Index+2], uint16(m+1))
|
||||
count2++
|
||||
}
|
||||
|
||||
hash2Peer.Storing = nil
|
||||
|
||||
for m := range hash2Peer.Closest {
|
||||
if isPacketSizeExceed(packetSize, peerRecordSize) { // check if minimum length is available in packet
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
hash2Peers = hash2Peers[n:]
|
||||
hash2Peer.Closest = hash2Peer.Closest[m:]
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
index := packetSize
|
||||
encodePeerRecord(raw[index:index+peerRecordSize], &hash2Peer.Closest[m], 0)
|
||||
|
||||
packetSize += peerRecordSize
|
||||
count2++
|
||||
binary.LittleEndian.PutUint16(raw[count2Index+0:count2Index+2], count2)
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint16(raw[count2Index+0:count2Index+2], count2|0x8000) // signal the last result for the key with bit 15
|
||||
binary.LittleEndian.PutUint16(raw[countIndex+0:countIndex+0+2], uint16(n+1)) // count of peer responses
|
||||
}
|
||||
|
||||
hash2Peers = nil
|
||||
}
|
||||
|
||||
// FIND_VALUE response embedded data
|
||||
if len(filesEmbed) > 0 {
|
||||
if isPacketSizeExceed(packetSize, 34+len(filesEmbed[0].Data)) { // check if there is enough space for at least the header and 1 record
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
for n, file := range filesEmbed {
|
||||
if isPacketSizeExceed(packetSize, 34+len(file.Data)) { // check if minimum length is available in packet
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
filesEmbed = filesEmbed[n:]
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
index := packetSize
|
||||
copy(raw[index:index+32], file.ID.Hash)
|
||||
binary.LittleEndian.PutUint16(raw[index+32:index+32+2], uint16(len(file.Data)))
|
||||
copy(raw[index+34:index+34+len(file.Data)], file.Data)
|
||||
|
||||
binary.LittleEndian.PutUint16(raw[countIndex+2:countIndex+2+2], uint16(n+1)) // count of embedded files
|
||||
packetSize += 34 + len(file.Data)
|
||||
}
|
||||
|
||||
filesEmbed = nil
|
||||
}
|
||||
|
||||
// Hashes not found
|
||||
if len(hashesNotFound) > 0 {
|
||||
index := packetSize
|
||||
|
||||
for n, hash := range hashesNotFound {
|
||||
if isPacketSizeExceed(packetSize, 32) { // check if there is enough space for at least the header and 1 record
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
continue createPacketLoop
|
||||
}
|
||||
|
||||
copy(raw[index+n*32:index+n*32+32], hash)
|
||||
|
||||
binary.LittleEndian.PutUint16(raw[countIndex+4:countIndex+4+2], uint16(n+1)) // count of hashes not found
|
||||
packetSize += 32
|
||||
}
|
||||
|
||||
hashesNotFound = nil
|
||||
}
|
||||
|
||||
raw[2] |= 1 << ActionSequenceLast // Indicate that no more responses will be sent in this sequence
|
||||
packetsRaw = append(packetsRaw, raw[:packetSize])
|
||||
|
||||
if len(hash2Peers) == 0 && len(filesEmbed) == 0 && len(hashesNotFound) == 0 { // this should always be the case here
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// encodePeerRecord encodes a single peer record and stores it into raw
|
||||
func encodePeerRecord(raw []byte, peer *PeerRecord, reason uint8) {
|
||||
copy(raw[0:0+33], peer.PublicKey.SerializeCompressed())
|
||||
binary.LittleEndian.PutUint32(raw[65:65+4], peer.LastContact)
|
||||
raw[69] = peer.Features | reason<<7
|
||||
|
||||
// IPv4
|
||||
copy(raw[33:33+4], peer.IPv4.To4())
|
||||
binary.LittleEndian.PutUint16(raw[37:37+2], peer.IPv4Port)
|
||||
binary.LittleEndian.PutUint16(raw[39:39+2], peer.IPv4PortReportedInternal)
|
||||
binary.LittleEndian.PutUint16(raw[41:41+2], peer.IPv4PortReportedExternal)
|
||||
|
||||
// IPv6
|
||||
copy(raw[43:43+16], peer.IPv6.To16())
|
||||
binary.LittleEndian.PutUint16(raw[59:59+2], peer.IPv6Port)
|
||||
binary.LittleEndian.PutUint16(raw[61:61+2], peer.IPv6PortReportedInternal)
|
||||
binary.LittleEndian.PutUint16(raw[63:63+2], peer.IPv6PortReportedExternal)
|
||||
}
|
||||
|
||||
// IsLast checks if the incoming message is the last expected response in this sequence.
|
||||
func (msg *MessageResponse) IsLast() bool {
|
||||
return msg.Actions&(1<<ActionSequenceLast) > 0
|
||||
}
|
||||
136
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding Transfer.go
generated
vendored
Normal file
136
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding Transfer.go
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
165
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding Traverse.go
generated
vendored
Normal file
165
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding Traverse.go
generated
vendored
Normal file
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
File Name: Message Encoding Traverse.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
*/
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
)
|
||||
|
||||
// MessageTraverse is the decoded traverse message.
|
||||
// It is sent by an original sender to a relay, to a final receiver (targert peer).
|
||||
type MessageTraverse struct {
|
||||
*MessageRaw // Underlying raw message.
|
||||
TargetPeer *btcec.PublicKey // End receiver peer ID.
|
||||
AuthorizedRelayPeer *btcec.PublicKey // Peer ID that is authorized to relay this message to the end receiver.
|
||||
Expires time.Time // Expiration time when this forwarded message becomes invalid.
|
||||
EmbeddedPacketRaw []byte // Embedded packet.
|
||||
SignerPublicKey *btcec.PublicKey // Public key that signed this message, ECDSA (secp256k1) 257-bit
|
||||
IPv4 net.IP // IPv4 address of the original sender. Set by authorized relay. 0 if not set.
|
||||
PortIPv4 uint16 // Port (actual one used for connection) of the original sender. Set by authorized relay.
|
||||
PortIPv4ReportedExternal uint16 // External port as reported by the original sender. This is used in case of port forwarding (manual or automated).
|
||||
IPv6 net.IP // IPv6 address of the original sender. Set by authorized relay. 0 if not set.
|
||||
PortIPv6 uint16 // Port (actual one used for connection) of the original sender. Set by authorized relay.
|
||||
PortIPv6ReportedExternal uint16 // External port as reported by the original sender. This is used in case of port forwarding (manual or automated).
|
||||
}
|
||||
|
||||
const traversePayloadHeaderSize = 76 + 65 + 28
|
||||
|
||||
// DecodeTraverse decodes a traverse message.
|
||||
// It does not verify if the receiver is authorized to read or forward this message.
|
||||
// It validates the signature, but does not validate the signer.
|
||||
func DecodeTraverse(msg *MessageRaw) (result *MessageTraverse, err error) {
|
||||
result = &MessageTraverse{
|
||||
MessageRaw: msg,
|
||||
}
|
||||
|
||||
if len(msg.Payload) < traversePayloadHeaderSize {
|
||||
return nil, errors.New("traverse: invalid minimum length")
|
||||
}
|
||||
|
||||
targetPeerIDcompressed := msg.Payload[0:33]
|
||||
authorizedRelayPeerIDcompressed := msg.Payload[33:66]
|
||||
|
||||
if result.TargetPeer, err = btcec.ParsePubKey(targetPeerIDcompressed, btcec.S256()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if result.AuthorizedRelayPeer, err = btcec.ParsePubKey(authorizedRelayPeerIDcompressed, btcec.S256()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// receiver and target must not be the same
|
||||
if result.TargetPeer.IsEqual(result.AuthorizedRelayPeer) {
|
||||
return nil, errors.New("traverse: target and relay invalid")
|
||||
}
|
||||
|
||||
expires64 := binary.LittleEndian.Uint64(msg.Payload[66 : 66+8])
|
||||
result.Expires = time.Unix(int64(expires64), 0)
|
||||
|
||||
sizePacketEmbed := binary.LittleEndian.Uint16(msg.Payload[74 : 74+2])
|
||||
if int(sizePacketEmbed) != len(msg.Payload)-traversePayloadHeaderSize {
|
||||
return nil, errors.New("traverse: size embedded packet mismatch")
|
||||
}
|
||||
|
||||
result.EmbeddedPacketRaw = msg.Payload[76 : 76+sizePacketEmbed]
|
||||
|
||||
signature := msg.Payload[76+sizePacketEmbed : 76+sizePacketEmbed+65]
|
||||
|
||||
result.SignerPublicKey, _, err = btcec.RecoverCompact(btcec.S256(), signature, HashData(msg.Payload[:76+sizePacketEmbed]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// IPv4
|
||||
ipv4B := make([]byte, 4)
|
||||
copy(ipv4B[:], msg.Payload[76+sizePacketEmbed+65:76+sizePacketEmbed+65+4])
|
||||
|
||||
result.IPv4 = ipv4B
|
||||
result.PortIPv4 = binary.LittleEndian.Uint16(msg.Payload[76+sizePacketEmbed+65+4 : 76+sizePacketEmbed+65+4+2])
|
||||
result.PortIPv4ReportedExternal = binary.LittleEndian.Uint16(msg.Payload[76+sizePacketEmbed+65+6 : 76+sizePacketEmbed+65+6+2])
|
||||
|
||||
// IPv6
|
||||
ipv6B := make([]byte, 16)
|
||||
copy(ipv6B[:], msg.Payload[76+sizePacketEmbed+65+8:76+sizePacketEmbed+65+8+16])
|
||||
|
||||
result.IPv6 = ipv6B
|
||||
result.PortIPv6 = binary.LittleEndian.Uint16(msg.Payload[76+sizePacketEmbed+65+24 : 76+sizePacketEmbed+65+24+2])
|
||||
result.PortIPv6ReportedExternal = binary.LittleEndian.Uint16(msg.Payload[76+sizePacketEmbed+65+26 : 76+sizePacketEmbed+65+26+2])
|
||||
|
||||
// TODO: Validate IPv4 and IPv6. Only external ones allowed.
|
||||
if result.IPv6.To4() != nil {
|
||||
return nil, errors.New("traverse: ipv6 address mismatch")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// EncodeTraverse encodes a traverse message
|
||||
func EncodeTraverse(senderPrivateKey *btcec.PrivateKey, embeddedPacketRaw []byte, receiverEnd *btcec.PublicKey, relayPeer *btcec.PublicKey) (packetRaw []byte, err error) {
|
||||
sizePacketEmbed := len(embeddedPacketRaw)
|
||||
if isPacketSizeExceed(traversePayloadHeaderSize, sizePacketEmbed) {
|
||||
return nil, errors.New("traverse encode: embedded packet too big")
|
||||
}
|
||||
|
||||
raw := make([]byte, traversePayloadHeaderSize+sizePacketEmbed)
|
||||
|
||||
targetPeerID := receiverEnd.SerializeCompressed()
|
||||
copy(raw[0:33], targetPeerID)
|
||||
authorizedRelayPeerID := relayPeer.SerializeCompressed()
|
||||
copy(raw[33:66], authorizedRelayPeerID)
|
||||
|
||||
expires64 := time.Now().Add(time.Hour).UTC().Unix()
|
||||
binary.LittleEndian.PutUint64(raw[66:66+8], uint64(expires64))
|
||||
|
||||
binary.LittleEndian.PutUint16(raw[74:74+2], uint16(sizePacketEmbed))
|
||||
copy(raw[76:76+sizePacketEmbed], embeddedPacketRaw)
|
||||
|
||||
// add signature
|
||||
signature, err := btcec.SignCompact(btcec.S256(), senderPrivateKey, HashData(raw[:76+sizePacketEmbed]), true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(raw[76+sizePacketEmbed:76+sizePacketEmbed+65], signature)
|
||||
|
||||
// IP and ports are to be filled by authorized relay peer
|
||||
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
// EncodeTraverseSetAddress sets the IP and Port in a traverse message that shall be forwarded to another peer
|
||||
func EncodeTraverseSetAddress(raw []byte, IPv4 net.IP, PortIPv4, PortIPv4ReportedExternal uint16, IPv6 net.IP, PortIPv6, PortIPv6ReportedExternal uint16) (err error) {
|
||||
if isPacketSizeExceed(len(raw), 0) {
|
||||
return errors.New("traverse encode 2: embedded packet too big")
|
||||
} else if len(raw) < traversePayloadHeaderSize {
|
||||
return errors.New("traverse encode 2: invalid packet")
|
||||
}
|
||||
|
||||
sizePacketEmbed := binary.LittleEndian.Uint16(raw[74 : 74+2])
|
||||
if int(sizePacketEmbed) != len(raw)-traversePayloadHeaderSize {
|
||||
return errors.New("traverse encode 2: size embedded packet mismatch")
|
||||
}
|
||||
|
||||
// IPv4
|
||||
if IPv4 != nil && len(IPv4) == net.IPv4len {
|
||||
copy(raw[76+sizePacketEmbed+65:76+sizePacketEmbed+65+4], IPv4.To4())
|
||||
binary.LittleEndian.PutUint16(raw[76+sizePacketEmbed+65+4:76+sizePacketEmbed+65+4+2], PortIPv4)
|
||||
binary.LittleEndian.PutUint16(raw[76+sizePacketEmbed+65+6:76+sizePacketEmbed+65+6+2], PortIPv4ReportedExternal)
|
||||
}
|
||||
|
||||
// IPv6
|
||||
if IPv6 != nil && len(IPv6) == net.IPv6len {
|
||||
copy(raw[76+sizePacketEmbed+65+8:76+sizePacketEmbed+65+8+16], IPv6.To16())
|
||||
binary.LittleEndian.PutUint16(raw[76+sizePacketEmbed+65+24:76+sizePacketEmbed+65+24+2], PortIPv6)
|
||||
binary.LittleEndian.PutUint16(raw[76+sizePacketEmbed+65+26:76+sizePacketEmbed+65+26+2], PortIPv6ReportedExternal)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
40
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding.go
generated
vendored
Normal file
40
vendor/github.com/PeernetOfficial/core/protocol/Message Encoding.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
File Name: Message Encoding.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
Intermediary between low-level packets and high-level interpretation.
|
||||
*/
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
)
|
||||
|
||||
// ProtocolVersion is the current protocol version
|
||||
const ProtocolVersion = 0
|
||||
|
||||
// MessageRaw is a high-level message between peers that has not been decoded
|
||||
type MessageRaw struct {
|
||||
PacketRaw
|
||||
SenderPublicKey *btcec.PublicKey // Sender Public Key, ECDSA (secp256k1) 257-bit
|
||||
SequenceInfo *SequenceExpiry // Sequence
|
||||
}
|
||||
|
||||
// The maximum packet size is = 65535 - 8 UDP byte header - 40 byte IPv6 header (IPv4 header is only 20 bytes).
|
||||
// However, due to the MTU soft limit and fragmentation, packets should be as small as possible.
|
||||
const udpMaxPacketSize = 65535 - 8 - 40
|
||||
|
||||
// internetSafeMTU is a value relatively safe to use for transmitting over the internet
|
||||
// Theory: The value is different for IPv4 (min 576 bytes, Ethernet 1500 bytes) and IPv6 (min 1280 bytes). 8 byte UDP header must be subtracted, as well as the IP header (20 bytes for IPv4, 40 for IPv6).
|
||||
// One simple test during development showed that 1500 - 8 - 40 - 8 worked for file transfer over IPv6 in Prague.
|
||||
// For IPv6 the internet recommends the minimal possible value: 1280 bytes.
|
||||
// This will be good enough for now. MTU negotiation that deviates from this value can be implemented separately (for example as part of file transfer).
|
||||
// Since packets may be sent at anytime via IPv4/IPv6 connections (even concurrently on multiple), there is a single MTU value here.
|
||||
const internetSafeMTU = 1280 - 8 - 40
|
||||
|
||||
// isPacketSizeExceed checks if the max packet size would be exceeded with the payload
|
||||
func isPacketSizeExceed(currentSize int, testSize int) bool {
|
||||
return currentSize+testSize > udpMaxPacketSize-PacketLengthMin
|
||||
}
|
||||
157
vendor/github.com/PeernetOfficial/core/protocol/Packet Encoding.go
generated
vendored
Normal file
157
vendor/github.com/PeernetOfficial/core/protocol/Packet Encoding.go
generated
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
File Name: Packet Encoding.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
Basic packet structure of ALL packets:
|
||||
Offset Size Info
|
||||
0 4 Nonce
|
||||
4 1 Protocol version = 0
|
||||
5 1 Command
|
||||
6 4 Sequence
|
||||
10 2 Size of payload data
|
||||
12 ? Payload
|
||||
? Randomized garbage
|
||||
? 65 Signature, ECDSA secp256k1 512-bit + 1 header byte
|
||||
|
||||
The peer ID of the sender, which is a ECDSA (secp256k1) 257-bit public key, can be extracted from the ECDSA signature.
|
||||
The signature is applied on the entire packet, which guarantees that the signature becomes invalid should someone try to forge the receiver (i.e. forward the packet).
|
||||
Because the signature could be a possible fingerpint, it is encrypted itself.
|
||||
*/
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/rand"
|
||||
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
"golang.org/x/crypto/salsa20"
|
||||
)
|
||||
|
||||
// PacketRaw is a decrypted P2P message
|
||||
type PacketRaw struct {
|
||||
Protocol uint8 // Protocol version = 0
|
||||
Command uint8 // 0 = Announcement
|
||||
Sequence uint32 // Sequence number
|
||||
Payload []byte // Payload
|
||||
}
|
||||
|
||||
// The minimum packet size is 12 bytes (minimum header size) + 65 bytes (signature)
|
||||
const PacketLengthMin = 12 + signatureSize
|
||||
const signatureSize = 65
|
||||
const maxRandomGarbage = 20
|
||||
|
||||
// PacketDecrypt decrypts the packet, verifies its signature and returns a high-level version of the packet.
|
||||
func PacketDecrypt(raw []byte, receiverPublicKey *btcec.PublicKey) (packet *PacketRaw, senderPublicKey *btcec.PublicKey, err error) {
|
||||
// Packet is assumed to be already checked for minimum length.
|
||||
|
||||
// Prepare Salsa20 nonce and key. Nonce = 2x first 4 bytes. For size reasons, only 4 bytes (instead of 8 bytes) is supplied in the packet.
|
||||
// This could be a risk, but considering we only use the PUBLIC key as decryption key, it is negligible.
|
||||
nonce := make([]byte, 8)
|
||||
copy(nonce[0:4], raw[0:4])
|
||||
copy(nonce[4:8], raw[0:4])
|
||||
|
||||
// Verify the signature and extract the public key from it.
|
||||
var signature [signatureSize]byte
|
||||
copy(signature[:], raw[len(raw)-signatureSize:])
|
||||
keySalsa := publicKeyToSalsa20Key(receiverPublicKey)
|
||||
salsa20.XORKeyStream(signature[:], signature[:], nonce, keySalsa)
|
||||
|
||||
senderPublicKey, _, err = btcec.RecoverCompact(btcec.S256(), signature[:], HashData(raw[:len(raw)-signatureSize]))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Decrypt the packet using Salsa20.
|
||||
bufferDecrypted := make([]byte, len(raw)-signatureSize-4) // full length -signature -nonce
|
||||
salsa20.XORKeyStream(bufferDecrypted[:], raw[4:len(raw)-signatureSize], nonce, keySalsa)
|
||||
|
||||
// copy all fields
|
||||
packet = &PacketRaw{Protocol: bufferDecrypted[0], Command: bufferDecrypted[1]}
|
||||
packet.Sequence = binary.LittleEndian.Uint32(bufferDecrypted[2:6])
|
||||
|
||||
sizePayload := binary.LittleEndian.Uint16(bufferDecrypted[6:8])
|
||||
if int(sizePayload) > len(bufferDecrypted)-8 { // invalid length?
|
||||
return nil, nil, errors.New("invalid length field")
|
||||
}
|
||||
if sizePayload > 0 {
|
||||
packet.Payload = make([]byte, int(sizePayload))
|
||||
copy(packet.Payload, bufferDecrypted[8:8+int(sizePayload)])
|
||||
}
|
||||
|
||||
return packet, senderPublicKey, nil
|
||||
}
|
||||
|
||||
// PacketEncrypt encrypts a packet using the provided senders private key and receivers compressed public key.
|
||||
func PacketEncrypt(senderPrivateKey *btcec.PrivateKey, receiverPublicKey *btcec.PublicKey, packet *PacketRaw) (raw []byte, err error) {
|
||||
garbage := packetGarbage(PacketLengthMin + len(packet.Payload))
|
||||
raw = make([]byte, PacketLengthMin+len(packet.Payload)+len(garbage))
|
||||
|
||||
nonceC := rand.Uint32()
|
||||
nonce := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint32(nonce[0:4], nonceC)
|
||||
binary.LittleEndian.PutUint32(nonce[4:8], nonceC)
|
||||
copy(raw[0:4], nonce[0:4])
|
||||
|
||||
raw[4] = packet.Protocol
|
||||
raw[5] = packet.Command
|
||||
|
||||
binary.LittleEndian.PutUint32(raw[6:10], uint32(packet.Sequence))
|
||||
binary.LittleEndian.PutUint16(raw[10:12], uint16(len(packet.Payload)))
|
||||
copy(raw[12:], packet.Payload)
|
||||
copy(raw[12+len(packet.Payload):12+len(packet.Payload)+len(garbage)], garbage)
|
||||
|
||||
// encrypt it using Salsa20
|
||||
keySalsa := publicKeyToSalsa20Key(receiverPublicKey)
|
||||
salsa20.XORKeyStream(raw[4:12+len(packet.Payload)+len(garbage)], raw[4:12+len(packet.Payload)+len(garbage)], nonce, keySalsa)
|
||||
|
||||
// add signature
|
||||
signature, err := btcec.SignCompact(btcec.S256(), senderPrivateKey, HashData(raw[:len(raw)-signatureSize]), true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
salsa20.XORKeyStream(signature[:], signature[:], nonce, keySalsa)
|
||||
copy(raw[len(raw)-signatureSize:], signature)
|
||||
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
func packetGarbage(packetLength int) (random []byte) {
|
||||
// Align maximum length at 508 bytes (UDP minimum no fragmentation) and at a relatively safe MTU.
|
||||
maxLength := maxRandomGarbage
|
||||
switch {
|
||||
case packetLength == 508, packetLength == internetSafeMTU:
|
||||
return nil
|
||||
case packetLength < 508 && (508-packetLength) < maxRandomGarbage:
|
||||
maxLength = 508 - packetLength
|
||||
case packetLength < internetSafeMTU && (internetSafeMTU-packetLength) < maxRandomGarbage:
|
||||
maxLength = internetSafeMTU - packetLength
|
||||
}
|
||||
|
||||
b := make([]byte, rand.Intn(maxLength))
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return nil
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func publicKeyToSalsa20Key(publicKey *btcec.PublicKey) (key *[32]byte) {
|
||||
// bit 0 from PublicKey.Y is ignored here, but is negligible for this purpose
|
||||
key = new([32]byte)
|
||||
copy(key[:], publicKey.SerializeCompressed()[1:])
|
||||
return key
|
||||
}
|
||||
|
||||
// SetSelfReportedPorts sets the fields Internal Port and External Port according to the connection details.
|
||||
// This is important for the remote peer to make smart decisions whether this peer is behind a NAT/firewall and supports port forwarding/UPnP.
|
||||
func (packet *PacketRaw) SetSelfReportedPorts(portI, portE uint16) {
|
||||
if packet.Command != CommandAnnouncement && packet.Command != CommandResponse { // only for Announcement and Response messages
|
||||
return
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint16(packet.Payload[19:19+2], portI)
|
||||
binary.LittleEndian.PutUint16(packet.Payload[21:21+2], portE)
|
||||
}
|
||||
199
vendor/github.com/PeernetOfficial/core/protocol/Packet Lite.go
generated
vendored
Normal file
199
vendor/github.com/PeernetOfficial/core/protocol/Packet Lite.go
generated
vendored
Normal file
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
File Name: Packet Lite.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
The lite packet header is used for encoding data transfer packets. The regular header is too expensive in terms of CPU consumption due to public key signing.
|
||||
Instead, a simple session ID will identify lite packets. The ID is randomized and only valid during the session.
|
||||
Unsolicited lite packets are therefore impossible; the receiver must have the ID already whitelisted for the packet to be recognized.
|
||||
|
||||
Offset Size Info
|
||||
0 16 ID
|
||||
16 2 Size of data to follow
|
||||
|
||||
*/
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// PacketLiteRaw is a decrypted P2P lite packet
|
||||
type PacketLiteRaw struct {
|
||||
ID uuid.UUID // ID
|
||||
Payload []byte // Payload
|
||||
Session *LiteID // Session info
|
||||
}
|
||||
|
||||
// Minimum packet size of lite packets.
|
||||
const PacketLiteSizeMin = 16 + 2
|
||||
|
||||
// IsPacketLite identifies a lite packet based on its ID. If the ID is not recognized, it fails.
|
||||
func (router *LiteRouter) IsPacketLite(raw []byte) (isLite bool, err error) {
|
||||
if len(raw) < PacketLiteSizeMin {
|
||||
return false, errors.New("invalid packet size")
|
||||
}
|
||||
|
||||
// Parse the ID and then look it up.
|
||||
var id uuid.UUID
|
||||
copy(id[:], raw[0:16])
|
||||
|
||||
return router.LookupLiteID(id) != nil, nil
|
||||
}
|
||||
|
||||
// PacketLiteDecode a lite packet. It will identify the lite packet based on its ID. If the ID is not recognized (which is the case for regular Peernet packets), the function fails.
|
||||
// It does not perform any decryption.
|
||||
func (router *LiteRouter) PacketLiteDecode(raw []byte) (packet *PacketLiteRaw, err error) {
|
||||
if len(raw) < PacketLiteSizeMin {
|
||||
return nil, errors.New("invalid packet size")
|
||||
}
|
||||
|
||||
// Parse the ID and look it up. It will contain information about the decryption algorithm to use.
|
||||
var id uuid.UUID
|
||||
copy(id[:], raw[0:16])
|
||||
|
||||
session := router.LookupLiteID(id)
|
||||
if session == nil {
|
||||
return nil, errors.New("packet ID not found")
|
||||
}
|
||||
|
||||
// TODO: Decrypt the data if indicated by the session.
|
||||
|
||||
sizePayload := binary.LittleEndian.Uint16(raw[16 : 16+2])
|
||||
if int(sizePayload) > len(raw)-PacketLiteSizeMin { // invalid size field?
|
||||
return nil, errors.New("invalid packet size field")
|
||||
}
|
||||
|
||||
// Valid packet received, extend expiration.
|
||||
session.expires = time.Now().Add(session.timeout)
|
||||
|
||||
return &PacketLiteRaw{Payload: raw[PacketLiteSizeMin:], ID: id, Session: session}, nil
|
||||
}
|
||||
|
||||
// Encodes a lite packet.
|
||||
func PacketLiteEncode(id uuid.UUID, data []byte) (raw []byte, err error) {
|
||||
raw = make([]byte, PacketLiteSizeMin+len(data))
|
||||
|
||||
copy(raw[0:16], id[:])
|
||||
binary.LittleEndian.PutUint16(raw[16:16+2], uint16(len(data)))
|
||||
copy(raw[PacketLiteSizeMin:], data)
|
||||
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
// ---- Lite packet ID management. This is similar to packet sequences. ----
|
||||
|
||||
// LiteRouter keeps track of accepted (expected) packet IDs.
|
||||
type LiteRouter struct {
|
||||
// list of recognized IDs
|
||||
ids map[uuid.UUID]*LiteID
|
||||
|
||||
sync.Mutex // synchronized access to the IDs
|
||||
}
|
||||
|
||||
// LiteID contains session information for a bidirectional transfer of data
|
||||
type LiteID struct {
|
||||
ID uuid.UUID // ID
|
||||
created time.Time // When the ID was created.
|
||||
expires time.Time // When the ID expires. This can be extended on the fly!
|
||||
Data interface{} // Optional high-level data associated with the ID
|
||||
timeout time.Duration // Timeout for receiving the next message
|
||||
invalidateFunc func() // Called on expiration.
|
||||
}
|
||||
|
||||
// Creates a new manager to keep track of accepted IDs.
|
||||
func NewLiteRouter() (router *LiteRouter) {
|
||||
router = &LiteRouter{
|
||||
ids: make(map[uuid.UUID]*LiteID),
|
||||
}
|
||||
|
||||
go router.autoDeleteExpired()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// autoDeleteExpired deletes all IDs that are expired.
|
||||
func (router *LiteRouter) autoDeleteExpired() {
|
||||
for {
|
||||
time.Sleep(4 * time.Second)
|
||||
now := time.Now()
|
||||
|
||||
router.Lock()
|
||||
for id, info := range router.ids {
|
||||
if info.expires.Before(now) {
|
||||
delete(router.ids, id)
|
||||
|
||||
if info.invalidateFunc != nil {
|
||||
go info.invalidateFunc()
|
||||
}
|
||||
}
|
||||
}
|
||||
router.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (router *LiteRouter) LookupLiteID(id uuid.UUID) (info *LiteID) {
|
||||
router.Lock()
|
||||
info = router.ids[id]
|
||||
router.Unlock()
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
// Returns a new lite ID to be used.
|
||||
func (router *LiteRouter) NewLiteID(data interface{}, timeout time.Duration, invalidateFunc func()) (info *LiteID) {
|
||||
info = &LiteID{
|
||||
created: time.Now(),
|
||||
expires: time.Now().Add(timeout),
|
||||
timeout: timeout,
|
||||
invalidateFunc: invalidateFunc,
|
||||
Data: data,
|
||||
ID: uuid.New(),
|
||||
}
|
||||
|
||||
router.Lock()
|
||||
router.ids[info.ID] = info
|
||||
router.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (router *LiteRouter) RegisterLiteID(id uuid.UUID, data interface{}, timeout time.Duration, invalidateFunc func()) (info *LiteID) {
|
||||
info = &LiteID{
|
||||
ID: id,
|
||||
created: time.Now(),
|
||||
expires: time.Now().Add(timeout),
|
||||
timeout: timeout,
|
||||
invalidateFunc: invalidateFunc,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
router.Lock()
|
||||
existingInfo := router.ids[info.ID]
|
||||
router.ids[info.ID] = info
|
||||
router.Unlock()
|
||||
|
||||
// Call the invalidate function if there is a collision. This should never happen.
|
||||
if existingInfo != nil && existingInfo.invalidateFunc != nil {
|
||||
go existingInfo.invalidateFunc()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Returns all lite sessions
|
||||
func (router *LiteRouter) All() (sessions []*LiteID) {
|
||||
router.Lock()
|
||||
for _, info := range router.ids {
|
||||
sessions = append(sessions, info)
|
||||
}
|
||||
router.Unlock()
|
||||
|
||||
return sessions
|
||||
}
|
||||
262
vendor/github.com/PeernetOfficial/core/protocol/Sequence.go
generated
vendored
Normal file
262
vendor/github.com/PeernetOfficial/core/protocol/Sequence.go
generated
vendored
Normal file
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
File Name: Sequence.go
|
||||
Copyright: 2021 Peernet s.r.o.
|
||||
Author: Peter Kleissner
|
||||
|
||||
This code caches and verifies message sequences. Sequence numbers are valid on a peer level, independent of which network connection was used.
|
||||
They can be used to map incoming response messages to previous outgoing requests. The remote peer ID is used together with a consecutive sequence number as unique key.
|
||||
|
||||
Sequences are created for:
|
||||
* Unidirectional messages (responses are one-way)
|
||||
* Bidirectional messages (responses may be sent back and forth). They use a different key namespace since remote sequence numbers could collide with locally created ones.
|
||||
|
||||
Advantages:
|
||||
* This secures against replay and poisoning attacks.
|
||||
* If used correctly it can also deduplicate messages (which occurs when 2 peers have multiple registered connections to each other but none are active and subsequent fallback to broadcast).
|
||||
* The round-trip time can be measured and used to determine the connection quality.
|
||||
* (future) It can be used to detect missed and lost replies.
|
||||
|
||||
*/
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/PeernetOfficial/core/btcec"
|
||||
)
|
||||
|
||||
// SequenceManager stores all message sequence numbers that are valid at the moment
|
||||
type SequenceManager struct {
|
||||
ReplyTimeout int // The round-trip timeout for message sequences.
|
||||
|
||||
// sequences is the list of sequence numbers that are valid at the moment. The value represents the time the sequence number.
|
||||
// Key = Peer ID + Sequence Number
|
||||
sequences map[string]*SequenceExpiry
|
||||
|
||||
sync.Mutex // synchronized access to the sequences
|
||||
}
|
||||
|
||||
// SequenceExpiry contains the decoded sequence information of a message.
|
||||
type SequenceExpiry struct {
|
||||
SequenceNumber uint32 // Sequence number
|
||||
created time.Time // When the sequence was created.
|
||||
expires time.Time // When the sequence expires. This can be extended on the fly!
|
||||
counter int // How many replies used the sequence. Multiple Response messages may be returned for a single Announcement one.
|
||||
Data interface{} // Optional high-level data associated with the sequence
|
||||
// bidirectional sequences only
|
||||
bidirectional bool // Whether this sequence is used in a bidirectional way
|
||||
timeout time.Duration // Timeout for receiving the next message
|
||||
invalidateFunc func() // The invalidation callback is in case a sequence collision or expiration invalidates the sequence.
|
||||
}
|
||||
|
||||
// NewSequenceManager creates a new sequence manager. The ReplyTimeout is in seconds. The expiration function is started immediately.
|
||||
func NewSequenceManager(ReplyTimeout int) (manager *SequenceManager) {
|
||||
manager = &SequenceManager{
|
||||
ReplyTimeout: ReplyTimeout,
|
||||
sequences: make(map[string]*SequenceExpiry),
|
||||
}
|
||||
|
||||
go manager.autoDeleteExpired()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// autoDeleteExpired deletes all sequences that are expired.
|
||||
func (manager *SequenceManager) autoDeleteExpired() {
|
||||
for {
|
||||
time.Sleep(time.Duration(manager.ReplyTimeout) * time.Second)
|
||||
now := time.Now()
|
||||
|
||||
manager.Lock()
|
||||
for key, sequence := range manager.sequences {
|
||||
if sequence.expires.Before(now) {
|
||||
delete(manager.sequences, key)
|
||||
|
||||
if sequence.invalidateFunc != nil {
|
||||
go sequence.invalidateFunc()
|
||||
}
|
||||
}
|
||||
}
|
||||
manager.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// sequence2Key creates the lookup key of a sequence for a peer.
|
||||
// Since bidirectional sequence numbers may be created from either side (remote or local peer), it does not share a namespace with unidirectional sequence numbers.
|
||||
func sequence2Key(bidirectional bool, publicKey *btcec.PublicKey, sequenceNumber uint32) (key string) {
|
||||
if !bidirectional {
|
||||
return "u" + string(publicKey.SerializeCompressed()) + strconv.FormatUint(uint64(sequenceNumber), 10)
|
||||
} else {
|
||||
return "b" + string(publicKey.SerializeCompressed()) + strconv.FormatUint(uint64(sequenceNumber), 10)
|
||||
}
|
||||
}
|
||||
|
||||
// NewSequence returns a new sequence and registers it. messageSequence must point to the variable holding the continuous next sequence number.
|
||||
// Use only for Announcement and Ping messages.
|
||||
func (manager *SequenceManager) NewSequence(publicKey *btcec.PublicKey, messageSequence *uint32, data interface{}) (info *SequenceExpiry) {
|
||||
info = &SequenceExpiry{
|
||||
SequenceNumber: atomic.AddUint32(messageSequence, 1),
|
||||
created: time.Now(),
|
||||
expires: time.Now().Add(time.Duration(manager.ReplyTimeout) * time.Second),
|
||||
Data: data,
|
||||
}
|
||||
|
||||
// Add the sequence to the list. Sequences are unique enough that collisions are unlikely and negligible.
|
||||
key := sequence2Key(false, publicKey, info.SequenceNumber)
|
||||
manager.Lock()
|
||||
manager.sequences[key] = info
|
||||
manager.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ArbitrarySequence returns an arbitrary sequence to be used for uncontacted peers
|
||||
func (manager *SequenceManager) ArbitrarySequence(publicKey *btcec.PublicKey, data interface{}) (info *SequenceExpiry) {
|
||||
info = &SequenceExpiry{
|
||||
SequenceNumber: rand.Uint32(),
|
||||
created: time.Now(),
|
||||
expires: time.Now().Add(time.Duration(manager.ReplyTimeout) * time.Second),
|
||||
Data: data,
|
||||
}
|
||||
|
||||
// Add the sequence to the list. Sequences are unique enough that collisions are unlikely and negligible.
|
||||
key := sequence2Key(false, publicKey, info.SequenceNumber)
|
||||
manager.Lock()
|
||||
manager.sequences[key] = info
|
||||
manager.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ValidateSequence validates the sequence number of an incoming message. It will set raw.sequence if valid.
|
||||
func (manager *SequenceManager) ValidateSequence(publicKey *btcec.PublicKey, sequenceNumber uint32, invalidate, extendValidity bool) (sequenceInfo *SequenceExpiry, valid bool, rtt time.Duration) {
|
||||
key := sequence2Key(false, publicKey, sequenceNumber)
|
||||
|
||||
manager.Lock()
|
||||
defer manager.Unlock()
|
||||
|
||||
// lookup the sequence
|
||||
sequence, ok := manager.sequences[key]
|
||||
if !ok {
|
||||
return nil, false, rtt
|
||||
}
|
||||
|
||||
// Initial reply: Store latest roundtrip time. That value might be distorted on Response vs Pong since Response messages might send data
|
||||
// up to 64 KB which obviously would be transmitted slower than an empty Pong reply. However, for the real world this is good enough.
|
||||
if sequence.counter == 0 {
|
||||
rtt = time.Since(sequence.created)
|
||||
}
|
||||
|
||||
sequence.counter++
|
||||
|
||||
// invalidate the sequence immediately?
|
||||
if invalidate {
|
||||
delete(manager.sequences, key)
|
||||
} else if extendValidity {
|
||||
// Special case CommandResponse: Extend validity in case there are follow-up responses, by half of the round-trip time since they will be sent one-way.
|
||||
sequence.expires = time.Now().Add(time.Duration(manager.ReplyTimeout) * time.Second / 2)
|
||||
}
|
||||
|
||||
return sequence, sequence.expires.After(time.Now()), rtt
|
||||
}
|
||||
|
||||
// InvalidateSequence invalidates the sequence number. It does not call invalidateFunc.
|
||||
func (manager *SequenceManager) InvalidateSequence(publicKey *btcec.PublicKey, sequenceNumber uint32, bidirectional bool) {
|
||||
key := sequence2Key(bidirectional, publicKey, sequenceNumber)
|
||||
|
||||
manager.Lock()
|
||||
delete(manager.sequences, key)
|
||||
manager.Unlock()
|
||||
}
|
||||
|
||||
// ---- bidirectional sequences ----
|
||||
|
||||
// RegisterSequenceBi registers a bidirectional sequence initiated by a remote peer. The caller must specify the timeout (which will be reset every time a new message appears in this sequence).
|
||||
// This is needed for bidirectional responses to accept subsequent incoming messages from the remote peer.
|
||||
func (manager *SequenceManager) RegisterSequenceBi(publicKey *btcec.PublicKey, sequenceNumber uint32, data interface{}, timeout time.Duration, invalidateFunc func()) (info *SequenceExpiry) {
|
||||
info = &SequenceExpiry{
|
||||
SequenceNumber: sequenceNumber,
|
||||
created: time.Now(),
|
||||
expires: time.Now().Add(timeout),
|
||||
timeout: timeout,
|
||||
invalidateFunc: invalidateFunc,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
// Before registering the sequence, check if there is a collision. If yes, invalidate the original one.
|
||||
key := sequence2Key(true, publicKey, info.SequenceNumber)
|
||||
manager.Lock()
|
||||
existingSequence := manager.sequences[key]
|
||||
manager.sequences[key] = info
|
||||
manager.Unlock()
|
||||
|
||||
// Call the invalidate function if there is a collision.
|
||||
if existingSequence != nil && existingSequence.invalidateFunc != nil {
|
||||
go existingSequence.invalidateFunc()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NewSequenceBi returns a new bidirectional sequence and registers it. messageSequence must point to the variable holding the continuous next sequence number.
|
||||
func (manager *SequenceManager) NewSequenceBi(publicKey *btcec.PublicKey, messageSequence *uint32, data interface{}, timeout time.Duration, invalidateFunc func()) (info *SequenceExpiry) {
|
||||
info = &SequenceExpiry{
|
||||
created: time.Now(),
|
||||
expires: time.Now().Add(timeout),
|
||||
bidirectional: true,
|
||||
timeout: timeout,
|
||||
invalidateFunc: invalidateFunc,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
manager.Lock()
|
||||
defer manager.Unlock()
|
||||
|
||||
// The likelihood of a collision is low but not impossible.
|
||||
for n := 0; n < 10000; n++ {
|
||||
info.SequenceNumber = atomic.AddUint32(messageSequence, 1)
|
||||
|
||||
key := sequence2Key(true, publicKey, info.SequenceNumber)
|
||||
if infoE := manager.sequences[key]; infoE == nil {
|
||||
manager.sequences[key] = info
|
||||
return info
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateSequenceBi validates the sequence number of an incoming message. It will set raw.sequence if valid.
|
||||
func (manager *SequenceManager) ValidateSequenceBi(publicKey *btcec.PublicKey, sequenceNumber uint32, isLast bool) (sequenceInfo *SequenceExpiry, valid bool, rtt time.Duration) {
|
||||
key := sequence2Key(true, publicKey, sequenceNumber)
|
||||
|
||||
manager.Lock()
|
||||
defer manager.Unlock()
|
||||
|
||||
// lookup the sequence
|
||||
sequence, ok := manager.sequences[key]
|
||||
if !ok {
|
||||
return nil, false, rtt
|
||||
}
|
||||
|
||||
// Initial reply: Store latest roundtrip time.
|
||||
if sequence.counter == 0 {
|
||||
rtt = time.Since(sequence.created)
|
||||
}
|
||||
|
||||
sequence.counter++
|
||||
|
||||
// invalidate the sequence immediately?
|
||||
if isLast {
|
||||
delete(manager.sequences, key)
|
||||
} else {
|
||||
sequence.expires = time.Now().Add(sequence.timeout)
|
||||
}
|
||||
|
||||
return sequence, sequence.expires.After(time.Now()), rtt
|
||||
}
|
||||
3
vendor/github.com/PeernetOfficial/core/protocol/readme.md
generated
vendored
Normal file
3
vendor/github.com/PeernetOfficial/core/protocol/readme.md
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Protocol
|
||||
|
||||
The Peernet Protocol is defined in the Whitepaper and implemented here accordingly.
|
||||
Reference in New Issue
Block a user