/
Packet Lite.go
199 lines (157 loc) · 5.34 KB
/
Packet Lite.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
/*
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
}