-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcommand.go
More file actions
205 lines (181 loc) · 6.09 KB
/
command.go
File metadata and controls
205 lines (181 loc) · 6.09 KB
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
package telnet
import (
"errors"
"fmt"
"strconv"
"strings"
)
// Telnet opcodes
const (
// EOR - End Of Record. The real meaning is implementation-specific, but these
// days IAC EOR is primarily used as an alternative to IAC GA that can indicate
// where a prompt is without all the historical baggage of GA
EOR byte = 239
// SE - Subnegotiation End. IAC SE is used to mark the end of a subnegotiation command
SE byte = 240
// NOP - No-Op. IAC NOP doesn't indicate anything at all, and this library ignores it.
NOP byte = 241
// DATAMARK - not in use by this library
DATAMARK byte = 242
// BRK - not in use by this library
BRK byte = 243
// IP - Not in use by this library
IP byte = 244
// AO - Not in use by this library
AO byte = 245
// AYT - If received, an IAC NOP will be sent in response
AYT byte = 246
// EC - Not in use by this library
EC byte = 247
// EL - Not in use by this library
EL byte = 248
// GA - Go Ahead. IAC GA is often used to indicate the end of a prompt line, so
// that clients know where to place a cursor. However, it was originally used for
// half-duplex terminals to indicate that the user could start typing and there is
// a lot of weird baggage around "kludge line mode", so it is usually preferable
// not to use this if the remote supports the EOR telopt.
GA byte = 249
// SB - Subnegotiation Begin. IAC SB is used to indicate the beginning of a subnegotiation
// command. These are telopt-specific commands that have telopt-specific meanings.
SB byte = 250
// WILL - IAC WILL is used to indicate that this terminal intends to activate a telopt
WILL byte = 251
// WONT - IAC WONT is used to indicate that this terminal refuses to activate a telopt
WONT byte = 252
// DO - IAC DO is used to request that the remote terminal activates a telopt
DO byte = 253
// DONT - IAC DONT is used to demand that the remote terminal do not activate a telopt
DONT byte = 254
// IAC - This opcode indicates the beginning of a new command
IAC byte = 255
)
var commandCodes = map[byte]string{
EOR: "EOR",
SE: "SE",
NOP: "NOP",
GA: "GA",
SB: "SB",
WILL: "WILL",
WONT: "WONT",
DO: "DO",
DONT: "DONT",
IAC: "IAC",
}
// Command is a struct that indicates some sort of IAC command either received from
// or sent to the remote. Any possible command can be represented by this struct.
type Command struct {
// OpCode is the code that comes after IAC in this command. Bear in mind that
// subnegotiations, which come in the form of IAC SB <bytes> IAC SE, are represented
// as a single command object with the OpCode of SB. IAC SE is never sent in its
// own command.
OpCode byte
// Option indicates which telopt this command is referring to, if the command has one.
// IAC WILL/WONT/DO/DONT/SB are always followed by a byte indicating a telopt.
Option TelOptCode
// Subnegotiation contains a byte slice containing the bytes, if any, that came
// between IAC SB and IAC SE. For non-SB commands, this slice is empty.
Subnegotiation []byte
}
// isActivateNegotiation indicates whether this command is a negotiation requesting activation
// of a telopt (DO/WILL).
func (c Command) isActivateNegotiation() bool {
return c.OpCode == DO || c.OpCode == WILL
}
// isLocalNegotiation indicates whether this command is a negotiation regarding a local
// telopt received from the remote (DO/DONT)
func (c Command) isLocalNegotiation() bool {
return c.OpCode == DO || c.OpCode == DONT
}
// reject produces a new command rejecting this one (WONT/DONT) if this command is
// an activate negotiation command (DO/WILL)
func (c Command) reject() Command {
var newOpCode byte
switch c.OpCode {
case DO:
newOpCode = WONT
case WILL:
newOpCode = DONT
default:
return Command{OpCode: NOP}
}
return Command{OpCode: newOpCode, Option: c.Option}
}
// accept produces a new command agreeing this one (WILL/DO/WONT/DONT) if this command is
// a negotiation command (DO/WILL/DONT/WONT)
func (c Command) agree() Command {
var newOpCode byte
switch c.OpCode {
case DO:
newOpCode = WILL
case WILL:
newOpCode = DO
case DONT:
newOpCode = WONT
case WONT:
newOpCode = DONT
default:
return Command{OpCode: NOP}
}
return Command{OpCode: newOpCode, Option: c.Option}
}
func parseCommand(data []byte) (Command, error) {
if data[0] != IAC {
return Command{}, fmt.Errorf("command did not begin with IAC: %q", commandStream(data))
}
if len(data) < 2 {
return Command{}, errors.New("command was just a standalone IAC with no opcode")
}
_, validOpcode := commandCodes[data[1]]
if !validOpcode {
return Command{}, fmt.Errorf("command did not have valid opcode: %q", commandStream(data))
}
if data[1] == NOP || data[1] == GA || data[1] == EOR {
return Command{
OpCode: data[1],
}, nil
}
if len(data) < 3 {
return Command{}, fmt.Errorf("command did not contain parameters: %q", commandStream(data))
}
if data[1] != SB {
return Command{
OpCode: data[1],
Option: TelOptCode(data[2]),
}, nil
}
if len(data) < 5 || data[len(data)-2] != IAC || data[len(data)-1] != SE {
return Command{}, fmt.Errorf("subnegotiation command did not end with IAC SE: %q", commandStream(data))
}
// doubled 255s in the subnegotiation data need to be pared down to a single 255 just like in the main
// text stream. We can do that by just compacting the data into the final slice
subnegotiationData := data[3 : len(data)-2]
finalBuffer := make([]byte, len(subnegotiationData))
bufferIndex, dataIndex := 0, 0
for ; dataIndex < len(subnegotiationData); bufferIndex++ {
finalBuffer[bufferIndex] = subnegotiationData[dataIndex]
dataIndex++
if subnegotiationData[bufferIndex] == IAC && dataIndex < len(subnegotiationData) && subnegotiationData[dataIndex] == IAC {
dataIndex++
}
}
return Command{
OpCode: data[1],
Option: TelOptCode(data[2]),
Subnegotiation: finalBuffer[:bufferIndex],
}, nil
}
func commandStream(b []byte) string {
var sb strings.Builder
for i := 0; i < len(b); i++ {
if i > 0 {
sb.WriteRune(' ')
}
code, hasCode := commandCodes[b[i]]
if !hasCode {
sb.WriteString(strconv.Itoa(int(b[i])))
} else {
sb.WriteString(code)
}
}
return sb.String()
}