Files
core/protocol/Packet Lite.go

200 lines
5.3 KiB
Go

/*
File Username: 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
}