-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
208 lines (189 loc) · 4.97 KB
/
main.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
package main
import (
"flag"
"fmt"
"io"
"log"
"net"
"os"
"path"
"strings"
"time"
)
var (
endpoint = flag.String("endpoint", "localhost:12300", "IP endpoint")
client = flag.Bool("client", false, "Run in client mode")
tz = flag.String("tz", "UTC", "Timezone to use in query")
Version = "unknown"
)
func main() {
flag.Usage = func() {
out := flag.CommandLine.Output()
name := path.Base(os.Args[0])
fmt.Fprintf(out, "%s (%s) - A small NXTP client and server.\n\n", name, Version)
fmt.Fprintf(out, "Usage:\n\n")
flag.PrintDefaults()
}
flag.Parse()
if *client {
runClient()
} else {
runServer()
}
}
func runClient() {
if len(*tz) > 60 {
log.Fatalf("Timezone too long: '%v'", *tz)
}
conn, err := net.Dial("tcp", *endpoint)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
dt, tm := makeRequest(conn, *tz)
fmt.Printf("Date: %s; Time: %s\n", dt, tm)
}
func makeRequest(conn io.ReadWriter, tz string) (string, string) {
reqBuf := make([]byte, 3+len(tz))
reqBuf[0] = 1
reqBuf[1] = byte(len(tz))
for i := 0; i < len(tz); i++ {
reqBuf[i+2] = tz[i]
}
setChecksum(reqBuf, len(reqBuf))
if err := sendBuffer(conn, reqBuf); err != nil {
log.Fatal(err)
}
resBuf := make([]byte, 22)
if _, err := recvBuffer(conn, resBuf); err != nil {
log.Fatal(err)
}
if resBuf[0] != 1 || resBuf[1] != 10 || resBuf[2] != 8 || !testChecksum(resBuf, len(resBuf)) {
log.Fatal("Bad response:", resBuf)
}
return string(resBuf[3:13]), string(resBuf[13:21])
}
func runServer() {
listener, err := net.Listen("tcp", *endpoint)
if err != nil {
log.Fatal(err)
}
for {
if conn, err := listener.Accept(); err != nil {
log.Println(err)
} else {
go handleConnection(conn)
}
}
}
func handleConnection(conn io.ReadWriteCloser) {
defer conn.Close()
// Version 1 request format:
//
// Name Size Notes
// -------- ----- -----------------------------------------------------
// Version 1 Always 1
// CodeLen 1 Length of Code in bytes. Between 0 and 60
// Code 0..60 Timezone code in ASCII
// Checksum 1 XOR checksum of the request
//
// Max size is 3 + 60 = 63
reqBuf := make([]byte, 63)
// This is call to .Read() is janky. It assumes that the whole request
// will come in at once and that a malicious client won't just send slow,
// byte-by-byte requests. I'd much prefer if I could describe the shape of
// the incoming data and the relationship between CodeLen and Code.
// Thankfully, the OS buffers should be significantly bigger than 63B, so
// this isn't a practical issue, but it bugs me regardless.
if n, err := conn.Read(reqBuf); err == nil {
codeLen := int(reqBuf[1])
if n < 3 || reqBuf[0] != 1 || codeLen != n-3 || !testChecksum(reqBuf, codeLen+3) {
log.Print("Malformed request:", reqBuf)
return
}
tz := string(reqBuf[2 : codeLen+2])
location, err := queryTimezone(tz)
if err != nil {
// Bad timezone: ignore requests
return
}
// Version 1 response format:
//
// Name Size Notes
// -------- ---- -----
// Version 1 Always 1
// DateLen 1 Length of Date field; always 10
// TimeLen 1 Length of Time field; always 8
// Date 10 Calendar date in dd/MM/yyyy format; ASCII
// Time 8 Time of day in HH:mm:ss format; ASCII
// Checksum 1 XOR checksum of the response
//
// Max size is 4 + 10 + 8 = 22 bytes
resBuf := make([]byte, 22)
now := time.Now().In(location)
payload := now.Format("02/01/200615:04:05")
resBuf[0] = 1
resBuf[1] = 10
resBuf[2] = 8
for i := 0; i < len(payload); i++ {
resBuf[i+3] = payload[i]
}
setChecksum(resBuf, len(resBuf))
if err := sendBuffer(conn, resBuf); err != nil {
log.Print(err)
return
}
}
}
func sendBuffer(conn io.Writer, buf []byte) error {
offset := 0
for offset < len(buf) {
n, err := conn.Write(buf[offset:])
if err != nil {
return fmt.Errorf("truncated respons: %w", err)
}
offset += n
}
return nil
}
func recvBuffer(conn io.Reader, buf []byte) (int, error) {
offset := 0
for offset < len(buf) {
var n int
var err error
if n, err = conn.Read(buf[offset:]); err != nil {
return 0, fmt.Errorf("truncated request: %w", err)
}
offset += n
}
return offset, nil
}
func calcChecksum(buf []byte, length int) byte {
var sum byte
sum = 123 // seed
for i := 0; i < length-1; i++ {
sum ^= buf[i]
}
return sum
}
func testChecksum(buf []byte, length int) bool {
return buf[length-1] == calcChecksum(buf, length)
}
func setChecksum(buf []byte, length int) {
buf[length-1] = calcChecksum(buf, length)
}
func normalizeTimezone(tz string) string {
normalizedTz := strings.ToLower(strings.ReplaceAll(tz, " ", ""))
if ianaTz, ok := windowsTZs[normalizedTz]; ok {
return ianaTz
}
// Fall back to treating it as an IANA timezone specification
return tz
}
func queryTimezone(tz string) (*time.Location, error) {
loc, err := time.LoadLocation(normalizeTimezone(tz))
if err != nil {
return nil, fmt.Errorf("could not query timezone %q: %w", tz, err)
}
return loc, nil
}