forked from koding/tunnel
/
util.go
161 lines (131 loc) · 2.9 KB
/
util.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
package tunnel
import (
"crypto/sha1"
"crypto/tls"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net"
"os"
"path/filepath"
"sync"
"time"
"github.com/cenkalti/backoff"
)
// async is a helper function to convert a blocking function to a function
// returning an error. Useful for plugging function closures into select and co
func async(fn func() error) <-chan error {
errChan := make(chan error, 0)
go func() {
select {
case errChan <- fn():
default:
}
close(errChan)
}()
return errChan
}
type expBackoff struct {
mu sync.Mutex
bk *backoff.ExponentialBackOff
}
func newForeverBackoff() *expBackoff {
eb := &expBackoff{
bk: backoff.NewExponentialBackOff(),
}
eb.bk.MaxElapsedTime = 0 // never stops
return eb
}
func (eb *expBackoff) NextBackOff() time.Duration {
eb.mu.Lock()
defer eb.mu.Unlock()
return eb.bk.NextBackOff()
}
func (eb *expBackoff) Reset() {
eb.mu.Lock()
eb.bk.Reset()
eb.mu.Unlock()
}
type callbacks struct {
mu sync.Mutex
name string
funcs map[string]func() error
}
func newCallbacks(name string) *callbacks {
return &callbacks{
name: name,
funcs: make(map[string]func() error),
}
}
func (c *callbacks) add(identifier string, fn func() error) {
c.mu.Lock()
c.funcs[identifier] = fn
c.mu.Unlock()
}
func (c *callbacks) pop(identifier string) (func() error, error) {
c.mu.Lock()
defer c.mu.Unlock()
fn, ok := c.funcs[identifier]
if !ok {
return nil, nil // nop
}
delete(c.funcs, identifier)
if fn == nil {
return nil, fmt.Errorf("nil callback set for %q client", identifier)
}
return fn, nil
}
func (c *callbacks) call(identifier string) error {
fn, err := c.pop(identifier)
if err != nil {
return err
}
if fn == nil {
return nil // nop
}
return fn()
}
// Returns server control url as a string. Reads scheme and remote address from connection.
func controlURL(conn net.Conn, controlPath string) string {
return fmt.Sprint(scheme(conn), "://", conn.RemoteAddr(), controlPath)
}
func scheme(conn net.Conn) (scheme string) {
switch conn.(type) {
case *tls.Conn:
scheme = "https"
default:
scheme = "http"
}
return
}
func signIdentifier(id string, key string) string {
// TODO replace with asymmetric encryption
sha := sha1.New()
sha.Write([]byte(id + ":" + key))
return base64.URLEncoding.EncodeToString(sha.Sum(nil))
}
func checkIdentifierSignature(id string, key string, signature string) bool {
return signature == signIdentifier(id, key)
}
func GetConfig(configPath *string, config any) error {
configFile, err := os.Open(*configPath)
if err != nil {
return err
}
// defer the closing of our configFile so that we can parse it later on
defer configFile.Close()
configBytes, err := io.ReadAll(configFile)
if err != nil {
return err
}
return json.Unmarshal(configBytes, &config)
}
func GetExecutableDir() string {
ex, err := os.Executable()
if err != nil {
panic(err)
}
exPath := filepath.Dir(ex)
return exPath
}