-
Notifications
You must be signed in to change notification settings - Fork 5
/
protocol_is.go
244 lines (208 loc) · 5.53 KB
/
protocol_is.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
// Copyright (c) 2016 Eric Barkie. All rights reserved.
// Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.
package aprs
import (
"bufio"
"bytes"
"context"
"fmt"
"net"
"net/http"
"strconv"
"strings"
"time"
)
func genLogin(user Addr, pass int) string {
return fmt.Sprintf("user %s pass %d vers %s %s", user, pass, SwName, SwVers)
}
func readLine(conn net.Conn, d time.Duration) (string, error) {
if d > 0 {
conn.SetReadDeadline(time.Now().Add(d))
} else {
conn.SetReadDeadline(time.Time{})
}
s, err := bufio.NewReader(conn).ReadString('\n')
return strings.TrimSpace(s), err
}
// GenPass generates a verification passcode for the given station.
func GenPass(call string) (pass uint16) {
// Refer to aprsc:
// https://github.com/hessu/aprsc
// Upper case callsign and strip SSID if it was included
c := strings.ToUpper(call)
dash := strings.Index(c, "-")
if dash > -1 {
c = c[:dash]
}
pass = 0x73e2 // The key/seed.
for i := 0; i < len(c); i += 2 {
pass ^= uint16(c[i]) << 8
pass ^= uint16(c[i+1])
}
// Mask off the high bit so number is always positive
pass &= 0x7fff
return
}
// RecvIS receives APRS-IS frames over tcp from the specified server.
// Filter(s) are optional and use the following syntax:
//
// http://www.aprs-is.net/javAPRSFilter.aspx
func RecvIS(ctx context.Context, dial string, user Addr, pass int, filters ...string) <-chan Frame {
fc := make(chan Frame)
go func() {
defer close(fc)
conn, err := net.Dial("tcp", dial)
if err != nil {
return
}
defer conn.Close()
// Read welcome banner
_, err = readLine(conn, 5*time.Second)
if err != nil {
return
}
// Login
login := genLogin(user, pass)
if len(filters) > 0 {
login += " filter " + strings.Join(filters, " ")
}
_, err = fmt.Fprintf(conn, "%s\r\n", login)
if err != nil {
return
}
// # logresp CWxxxx unverified, server CWOP-7
// # logresp CWxxxx unverified, server THIRD
_, err = readLine(conn, 5*time.Second)
if err != nil {
return
}
// Listen for frames until either the connection is closed or a
// context cancel is received.
var s string
for {
select {
case <-ctx.Done():
return
default:
}
// Heartbeats come across every 20 seconds so that's the
// longest the read should block. It's also the longest
// it would take for a context cancel to be processed.
s, err = readLine(conn, 30*time.Second)
if err != nil {
return
}
// # aprsc 2.1.4-g408ed49 26 Aug 2017 16:49:48 GMT FIFTH 44.74.128.25:14580
if !strings.HasPrefix(s, "#") {
f := Frame{}
err = f.FromString(s)
if err != nil {
continue
}
fc <- f
}
}
}()
return fc
}
// SendIS sends a Frame to the specified APRS-IS dial string. The
// dial string should be in the form scheme://host:port with
// scheme being http, tcp, or udp. This is most commonly used for
// CWOP.
func (f Frame) SendIS(dial string, pass int) error {
// Refer to Connecting to APRS-IS:
// http://www.aprs-is.net/Connecting.aspx
parts := strings.Split(strings.ToLower(dial), "://")
if len(parts) != 2 {
return net.InvalidAddrError(dial)
}
switch parts[0] {
case "http":
return f.SendHTTP(dial, pass)
case "tcp":
return f.SendTCP(parts[1], pass)
case "udp":
return f.SendUDP(parts[1], pass)
}
return ErrProtoScheme
}
// SendHTTP sends a Frame to the specified APRS-IS host over the
// HTTP protocol. This scheme is the least efficient and requires
// a verified connection (real callsign and passcode) but is
// reliable and provides acknowledgement of receipt.
func (f Frame) SendHTTP(dial string, pass int) (err error) {
if pass < 0 {
err = ErrCallNotVerified
return
}
data := fmt.Sprintf("%s\r\n%s", genLogin(f.Src, pass), f)
var req *http.Request
req, err = http.NewRequest("POST", dial, bytes.NewBufferString(data))
if err != nil {
return
}
req.Header.Set("Accept-Type", "text/plain")
req.Header.Set("Content-Type", "application/octet-stream")
req.Header.Set("Content-Length", strconv.Itoa(len(data)))
client := &http.Client{}
var resp *http.Response
resp, err = client.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("HTTP request returned non-OK status code %d", resp.StatusCode)
}
return
}
// SendUDP sends a Frame to the specified APRS-IS host over the
// UDP protocol. This scheme is the most efficient but requires
// a verified connection (real callsign and passcode) and has no
// acknowledgement of receipt.
func (f Frame) SendUDP(dial string, pass int) (err error) {
if pass < 0 {
err = ErrCallNotVerified
return
}
var conn net.Conn
conn, err = net.Dial("udp", dial)
if err != nil {
return
}
defer conn.Close()
// Send data packet
_, err = fmt.Fprintf(conn, "%s\r\n%s", genLogin(f.Src, pass), f)
return
}
// SendTCP sends a Frame to the specified APRS-IS host over the
// TCP protocol. This scheme is the oldest, most compatible, and
// allows unverified connections.
func (f Frame) SendTCP(dial string, pass int) (err error) {
var conn net.Conn
conn, err = net.Dial("tcp", dial)
if err != nil {
return
}
defer conn.Close()
// Read welcome banner
_, err = readLine(conn, 5*time.Second)
if err != nil {
return
}
// Login
_, err = fmt.Fprintf(conn, "%s\r\n", genLogin(f.Src, pass))
if err != nil {
return
}
// # logresp CWxxxx unverified, server CWOP-7
// # logresp CWxxxx unverified, server THIRD
_, err = readLine(conn, 5*time.Second)
if err != nil {
return
}
// Send frame
_, err = fmt.Fprintf(conn, "%s\r\n", f)
return
}