-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
teonet.go
369 lines (329 loc) · 9.13 KB
/
teonet.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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
// Copyright 2021-2023 Kirill Scherba <kirill@scherba.ru>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Teonet v4
package teonet
import (
"errors"
"flag"
"fmt"
"math/rand"
"os"
"time"
"github.com/teonet-go/tru"
"github.com/teonet-go/tru/hotkey"
"github.com/teonet-go/tru/teolog"
)
const Version = "0.6.6"
// Reset random and use rnd instead
var rnd = rand.New(rand.NewSource(time.Now().Unix()))
// Teonet data structure and methods receiver
type Teonet struct {
config *config
tru *tru.Tru
log *teolog.Teolog
clientReaders *clientReaders
subscribers *subscribers
channels *channels
connectURL *connectURL
peerRequests *connectRequests
connRequests *connectRequests
puncher *puncher
closing chan interface{}
}
type Treceivecb func(teo *Teonet, c *Channel, p *Packet, e *Event) bool
type TreceivecbShort func(c *Channel, p *Packet, e *Event) bool
type LogFilter = teolog.Filter
type Stat = tru.Stat
type Hotkey = tru.Hotkey
type MaxDataLen = tru.MaxDataLenType
// Error peer does not connected
var ErrPeerNotConnected = errors.New("peer does not connected")
// log is global pointer to teonet log based on go log
var log *teolog.Teolog
// Log get teonet log to use it in application and inside teonet
func Log() *teolog.Teolog { return log }
// Logfilter return string in teolog.Logfilter type
func Logfilter(str string) teolog.Filter { return teolog.Logfilter(str) }
// Logo print teonet logo
func Logo(title, ver string) { fmt.Println(LogoString(title, ver)) }
// LogoString return teonet logo in string
func LogoString(title, ver string) string {
return fmt.Sprint("" +
" _____ _ \n" +
"|_ _|__ ___ _ __ ___| |_ v5\n" +
" | |/ _ \\/ _ \\| '_ \\ / _ \\ __|\n" +
" | | __/ (_) | | | | __/ |_ \n" +
" |_|\\___|\\___/|_| |_|\\___|\\__|\n" +
"\n" +
title + " ver " + ver + ", based on Teonet v5 ver " + Version +
"\n",
)
}
// Check requered application parameters
func CheckRequeredParams(req ...string) {
// Return if input requered array is empty
if len(req) == 0 {
return
}
// Convert input parameters to map
requered := make(map[string]bool)
for _, r := range req {
requered[r] = true
}
// Check if flag value is empty and this value in requered map
flag.VisitAll(func(f *flag.Flag) {
if f.Value.String() == "" {
if b, ok := requered[f.Name]; ok && b {
fmt.Println("Parameters error: the '" + f.Name +
"' parameter required\n")
flag.Usage()
os.Exit(0)
}
}
})
}
// reader is Main teonet reader
func reader(teo *Teonet, c *Channel, p *Packet, e *Event) {
// Delete channel on err after all other reader process this error
// TODO: Really need this defer?
defer func() {
if e.Err != nil {
teo.channels.del(c, false)
}
}()
// Process commect messages
if e.Event == EventData && (teo.connectToPeer(c, p) || teo.connectToClient(c, p)) {
return
}
// Send to subscribers readers (to readers from teo.subscribe)
if teo.subscribers.send(teo, c, p, e) {
return
}
// Send to client readers (to reader from teonet.Init)
teo.clientReaders.send(teo, c, p, e)
}
// New create new teonet connection. The attr parameters:
//
// int port number to teonet listen
// string internal log Level to show teonet debug messages
// Stat set true to show tru statistic table
// Hotkey start hotkey meny
// MaxDataLen set max data length
// *teolog.Teolog teonet logger
// ApiInterface api interface
// OsConfigDir os directory to save config
// func(c *Channel, p *Packet, e *Event) - message receiver
// func(t *Teonet, c *Channel, p *Packet, e *Event) - message receiver
func New(appName string, attr ...interface{}) (teo *Teonet, err error) {
// Atribute struct
var param struct {
port int
stat tru.Stat
hotkey tru.Hotkey
maxDataLen tru.MaxDataLenType
logLevel string
logFilter LogFilter
log *teolog.Teolog
reader Treceivecb
api ApiInterface
configDir OsConfigDir
}
// Set default
// Teonet applications in some hosts can't receive max UDP packets, so
// we set default max data length to 1024 bytes. This packet size will
// awailable for any hosts.
param.maxDataLen = 1024
// Parse attributes
for i := range attr {
switch d := attr[i].(type) {
// Local port
case int:
param.port = d
// Log level
case string:
param.logLevel = d
// Log filter
case teolog.Filter:
param.logFilter = d
// Show tru statistic flag
case tru.Stat:
param.stat = d
// Start hotkey menu
case tru.Hotkey:
param.hotkey = d
// Max data length
case tru.MaxDataLenType:
param.maxDataLen = d
// Logger
case *teolog.Teolog:
param.log = d
// Treceivecb:
case func(teo *Teonet, c *Channel, p *Packet, e *Event) bool:
param.reader = d
// TreceivecbShort:
case func(c *Channel, p *Packet, e *Event) bool:
param.reader = func(t *Teonet, c *Channel, p *Packet, e *Event) bool {
return d(c, p, e)
}
// API interface
case ApiInterface:
param.api = d
// Config file folder
case OsConfigDir:
param.configDir = d
// Some enother (incorrect) attribute
default:
err = fmt.Errorf("incorrect attribute type '%T'", d)
return
}
}
// Set log and loglevel
if param.logLevel == "" {
param.logLevel = "NONE"
}
if param.log != nil {
log = param.log
} else if log == nil {
log = teolog.New()
}
// Create new teonet holder
teo = new(Teonet)
teo.closing = make(chan interface{}, 1)
teo.newConnectURL()
teo.newSubscribers()
teo.newPeerRequests()
teo.newConnRequests()
teo.newClientReaders()
teo.log = log
// Create config holder and read config
err = teo.newConfig(appName, string(param.configDir))
if err != nil {
return
}
// Add api and client readers
teo.addApiReader(param.api)
teo.clientReaders.add(param.reader)
// Init tru and start listen port to get messages
teo.tru, err = tru.New(param.port, param.stat, param.hotkey, param.maxDataLen,
teo.log, param.logLevel, param.logFilter, teo.config.trudpPrivateKey,
// Receive data callback
func(c *tru.Channel, p *tru.Packet, err error) bool {
auth := teo.getAuth()
ch, ok := teo.channels.get(c)
if !ok {
if auth != nil && c == auth.c {
// There is Auth channel
ch = auth
} else {
// Create new channel for not error packets
if err != nil {
return false
}
ch = teo.channels.new(c)
}
}
// Create packet
var pac *Packet
if p != nil {
pac = &Packet{p, ch.a, false}
}
// Create Disconnect, TeonetDisconnect or Data Events
e := new(Event)
if err != nil {
e.Err = err
if ch == auth {
e.Event = EventTeonetDisconnected
} else {
e.Event = EventDisconnected
}
} else {
e.Event = EventData
}
// Send packet and event to main teonet reader
// TODO: add return bool to reader func or not add :-)
reader(teo, ch, pac, e)
return true
},
// Connect to this server callback
func(c *tru.Channel, err error) {
// Wait this tru channel connected to teonet channel and delete
// it if not connected during timeout
_, exists := teo.channels.get(c)
if exists {
return
}
go func(c *tru.Channel) {
time.Sleep(tru.ClientConnectTimeout)
ch, exists := teo.channels.get(c)
if !exists {
if newch, ok := teo.channels.getByIP(c.Addr().String()); !ok {
if !c.Destroyed() {
log.Debug.Println("remove dummy tru channel:", c, ch)
c.Close()
}
} else {
log.Debugvv.Println("tru channel was reconnected:", c.Addr().String(), newch)
}
} else if ch.IsNew() {
log.Debug.Println("remove dummy(new) teonet channel:", c, ch)
teo.channels.del(ch)
}
}(c)
},
)
if err != nil {
log.Error.Println("can't initial tru, error:", err)
return
}
teo.newChannels()
teo.newPuncher()
log.Connect.Println("start listen teonet at port", teo.tru.LocalPort())
return
}
// Close all channels
func (teo *Teonet) Close() {
close(teo.closing)
teo.tru.Close()
}
// RHost return current auth server
func (teo Teonet) RHost() *Channel { return teo.getAuth() }
// Hotkey return pointer to hotkey menu used in tru or nil if hotkey menu does
// not start
func (teo Teonet) Hotkey() *hotkey.Hotkey { return teo.tru.Hotkey() }
// ShowTrudp show/stop tru statistic
func (teo Teonet) ShowTrudp(set bool) {
if set {
teo.tru.StatisticPrint()
} else {
teo.tru.StatisticPrintStop()
}
}
// Send data to peer
func (teo *Teonet) SendTo(addr string, data []byte, attr ...interface{}) (id int, err error) {
// Check address
c, ok := teo.channels.get(addr)
if !ok {
err = ErrPeerNotConnected
return
}
// Add teo to attr, it need for subscribe to answer func
if len(attr) > 0 {
attr = append([]interface{}{teo}, attr...)
}
// Send data to channel
return c.Send(data, attr...)
}
// Connected return true if peer with selected address is connected now
func (teo *Teonet) Connected(addr string) (ok bool) {
_, ok = teo.channels.get(addr)
return
}
// Log get teonet log
func (teo Teonet) Log() *teolog.Teolog {
return teo.log
}
// Port get teonet local port
func (teo Teonet) Port() int {
return teo.tru.LocalPort()
}