/src/PcapPlusPlus/Packet++/src/TelnetLayer.cpp
Line | Count | Source (jump to first uncovered line) |
1 | 8.91k | #define LOG_MODULE PacketLogModuleTelnetLayer |
2 | | |
3 | | #include "TelnetLayer.h" |
4 | | #include "Logger.h" |
5 | | #include "GeneralUtils.h" |
6 | | #include <cstring> |
7 | | |
8 | | namespace pcpp |
9 | | { |
10 | | |
11 | | bool TelnetLayer::isDataField(uint8_t* pos) const |
12 | 129k | { |
13 | | // "FF FF" means data |
14 | 129k | return pos[0] != static_cast<int>(TelnetCommand::InterpretAsCommand) || |
15 | 129k | pos[1] == static_cast<int>(TelnetCommand::InterpretAsCommand); |
16 | 129k | } |
17 | | |
18 | | bool TelnetLayer::isCommandField(uint8_t* pos) const |
19 | 110k | { |
20 | 110k | return !isDataField(pos); |
21 | 110k | } |
22 | | |
23 | | size_t TelnetLayer::distanceToNextIAC(uint8_t* startPos, size_t maxLength) |
24 | 51.7k | { |
25 | 51.7k | uint8_t* pos = nullptr; |
26 | 51.7k | size_t addition = 0; |
27 | 51.7k | size_t currentOffset = 0; |
28 | 51.7k | do |
29 | 90.4k | { |
30 | | // If it is second turn position should be adjusted to after second FF |
31 | 90.4k | if (addition) |
32 | 38.7k | addition += 2; |
33 | | |
34 | 90.4k | pos = (uint8_t*)memchr(startPos + currentOffset + 1, static_cast<int>(TelnetCommand::InterpretAsCommand), |
35 | 90.4k | maxLength - currentOffset); |
36 | 90.4k | if (pos) |
37 | 84.3k | addition += pos - (startPos + currentOffset); |
38 | 6.13k | else |
39 | 6.13k | addition += maxLength - currentOffset; |
40 | 90.4k | currentOffset = currentOffset + addition; |
41 | | // "FF FF" means data continue |
42 | 90.4k | } while (pos && ((pos + 1) < (startPos + maxLength)) && |
43 | 90.4k | (pos[1] == static_cast<int>(TelnetCommand::InterpretAsCommand)) && (currentOffset < maxLength)); |
44 | | |
45 | 51.7k | return addition; |
46 | 51.7k | } |
47 | | |
48 | | size_t TelnetLayer::getFieldLen(uint8_t* startPos, size_t maxLength) |
49 | 129k | { |
50 | | // Check first byte is IAC |
51 | 129k | if (startPos && (startPos[0] == static_cast<int>(TelnetCommand::InterpretAsCommand)) && (maxLength >= 2)) |
52 | 92.7k | { |
53 | | // If subnegotiation parse until next IAC |
54 | 92.7k | if (startPos[1] == static_cast<int>(TelnetCommand::Subnegotiation)) |
55 | 14.5k | return distanceToNextIAC(startPos, maxLength); |
56 | | // Only WILL, WONT, DO, DONT have option. Ref http://pcmicro.com/netfoss/telnet.html |
57 | 78.2k | else if (startPos[1] >= static_cast<int>(TelnetCommand::WillPerform) && |
58 | 78.2k | startPos[1] <= static_cast<int>(TelnetCommand::DontPerform)) |
59 | 8.27k | return 3; |
60 | 69.9k | return 2; |
61 | 92.7k | } |
62 | 37.1k | return distanceToNextIAC(startPos, maxLength); |
63 | 129k | } |
64 | | |
65 | | uint8_t* TelnetLayer::getNextDataField(uint8_t* pos, size_t len) |
66 | 4.12k | { |
67 | 4.12k | size_t offset = 0; |
68 | 6.37k | while (offset < len) |
69 | 6.37k | { |
70 | | // Move to next field |
71 | 6.37k | size_t length = getFieldLen(pos, len - offset); |
72 | 6.37k | pos += length; |
73 | 6.37k | offset += length; |
74 | | |
75 | 6.37k | if (isDataField(pos)) |
76 | 4.12k | return pos; |
77 | 6.37k | } |
78 | | |
79 | 0 | return nullptr; |
80 | 4.12k | } |
81 | | |
82 | | uint8_t* TelnetLayer::getNextCommandField(uint8_t* pos, size_t len) |
83 | 59.5k | { |
84 | 59.5k | size_t offset = 0; |
85 | 110k | while (offset < len) |
86 | 99.6k | { |
87 | | // Move to next field |
88 | 99.6k | size_t length = getFieldLen(pos, len - offset); |
89 | 99.6k | pos += length; |
90 | 99.6k | offset += length; |
91 | | |
92 | 99.6k | if ((static_cast<size_t>(pos - m_Data) <= (m_DataLen - 2)) && |
93 | 99.6k | isCommandField(pos)) // Need at least 2 bytes for command |
94 | 48.8k | return pos; |
95 | 99.6k | } |
96 | | |
97 | 10.6k | return nullptr; |
98 | 59.5k | } |
99 | | |
100 | | int16_t TelnetLayer::getSubCommand(uint8_t* pos, size_t len) |
101 | 11.9k | { |
102 | 11.9k | if (len < 3 || pos[1] < static_cast<int>(TelnetCommand::Subnegotiation)) |
103 | 9.11k | return static_cast<int>(TelnetOption::TelnetOptionNoOption); |
104 | 2.85k | return pos[2]; |
105 | 11.9k | } |
106 | | |
107 | | uint8_t* TelnetLayer::getCommandData(uint8_t* pos, size_t& len) |
108 | 11.9k | { |
109 | 11.9k | if (pos[1] == static_cast<int>(TelnetCommand::Subnegotiation) && len > 3) |
110 | 717 | { |
111 | 717 | len -= 3; |
112 | 717 | return &pos[3]; |
113 | 717 | } |
114 | 11.2k | len = 0; |
115 | 11.2k | return nullptr; |
116 | 11.9k | } |
117 | | |
118 | | std::string TelnetLayer::getDataAsString(bool removeEscapeCharacters) |
119 | 8.26k | { |
120 | 8.26k | uint8_t* dataPos = nullptr; |
121 | 8.26k | if (isDataField(m_Data)) |
122 | 4.14k | dataPos = m_Data; |
123 | 4.12k | else |
124 | 4.12k | dataPos = getNextDataField(m_Data, m_DataLen); |
125 | | |
126 | 8.26k | if (!dataPos) |
127 | 0 | { |
128 | 0 | PCPP_LOG_DEBUG("Packet does not have a data field"); |
129 | 0 | return std::string(); |
130 | 0 | } |
131 | | |
132 | | // Convert to string |
133 | 8.26k | if (removeEscapeCharacters) |
134 | 8.26k | { |
135 | 8.26k | std::stringstream ss; |
136 | 171k | for (size_t idx = 0; idx < m_DataLen - (dataPos - m_Data) + 1; ++idx) |
137 | 163k | { |
138 | 163k | if (int(dataPos[idx]) < 127 && int(dataPos[idx]) > 31) // From SPACE to ~ |
139 | 14.3k | ss << dataPos[idx]; |
140 | 163k | } |
141 | 8.26k | return ss.str(); |
142 | 8.26k | } |
143 | 0 | return std::string((char*)m_Data, m_DataLen); |
144 | 8.26k | } |
145 | | |
146 | | size_t TelnetLayer::getTotalNumberOfCommands() |
147 | 2.22k | { |
148 | 2.22k | size_t ctr = 0; |
149 | 2.22k | if (isCommandField(m_Data)) |
150 | 907 | ++ctr; |
151 | | |
152 | 2.22k | uint8_t* pos = m_Data; |
153 | 9.58k | while (pos != nullptr) |
154 | 7.35k | { |
155 | 7.35k | size_t offset = pos - m_Data; |
156 | 7.35k | pos = getNextCommandField(pos, m_DataLen - offset); |
157 | 7.35k | if (pos) |
158 | 5.12k | ++ctr; |
159 | 7.35k | } |
160 | | |
161 | 2.22k | return ctr; |
162 | 2.22k | } |
163 | | |
164 | | size_t TelnetLayer::getNumberOfCommands(TelnetCommand command) |
165 | 8.26k | { |
166 | 8.26k | if (static_cast<int>(command) < 0) |
167 | 2.22k | return 0; |
168 | | |
169 | 6.03k | size_t ctr = 0; |
170 | 6.03k | if (isCommandField(m_Data) && m_Data[1] == static_cast<int>(command)) |
171 | 1.09k | ++ctr; |
172 | | |
173 | 6.03k | uint8_t* pos = m_Data; |
174 | 30.4k | while (pos != nullptr) |
175 | 24.4k | { |
176 | 24.4k | size_t offset = pos - m_Data; |
177 | 24.4k | pos = getNextCommandField(pos, m_DataLen - offset); |
178 | 24.4k | if (pos && pos[1] == static_cast<int>(command)) |
179 | 6.57k | ++ctr; |
180 | 24.4k | } |
181 | | |
182 | 6.03k | return ctr; |
183 | 8.26k | } |
184 | | |
185 | | TelnetLayer::TelnetCommand TelnetLayer::getFirstCommand() |
186 | 2.22k | { |
187 | | // If starts with command |
188 | 2.22k | if (isCommandField(m_Data)) |
189 | 907 | return static_cast<TelnetCommand>(m_Data[1]); |
190 | | |
191 | | // Check is there any command |
192 | 1.32k | uint8_t* pos = getNextCommandField(m_Data, m_DataLen); |
193 | 1.32k | if (pos) |
194 | 1.12k | return static_cast<TelnetCommand>(pos[1]); |
195 | 194 | return TelnetCommand::TelnetCommandEndOfPacket; |
196 | 1.32k | } |
197 | | |
198 | | TelnetLayer::TelnetCommand TelnetLayer::getNextCommand() |
199 | 8.26k | { |
200 | 8.26k | if (lastPositionOffset == SIZE_MAX) |
201 | 2.22k | { |
202 | 2.22k | lastPositionOffset = 0; |
203 | 2.22k | if (isCommandField(m_Data)) |
204 | 907 | return static_cast<TelnetLayer::TelnetCommand>(m_Data[1]); |
205 | 2.22k | } |
206 | | |
207 | 7.35k | uint8_t* pos = getNextCommandField(&m_Data[lastPositionOffset], m_DataLen - lastPositionOffset); |
208 | 7.35k | if (pos) |
209 | 5.12k | { |
210 | 5.12k | lastPositionOffset = pos - m_Data; |
211 | 5.12k | return static_cast<TelnetLayer::TelnetCommand>(pos[1]); |
212 | 5.12k | } |
213 | 2.22k | lastPositionOffset = SIZE_MAX; |
214 | 2.22k | return TelnetCommand::TelnetCommandEndOfPacket; |
215 | 7.35k | } |
216 | | |
217 | | TelnetLayer::TelnetOption TelnetLayer::getOption() |
218 | 8.26k | { |
219 | 8.26k | if (lastPositionOffset < m_DataLen) |
220 | 5.94k | return static_cast<TelnetOption>(getSubCommand( |
221 | 5.94k | &m_Data[lastPositionOffset], getFieldLen(&m_Data[lastPositionOffset], m_DataLen - lastPositionOffset))); |
222 | 2.32k | return TelnetOption::TelnetOptionNoOption; |
223 | 8.26k | } |
224 | | |
225 | | TelnetLayer::TelnetOption TelnetLayer::getOption(TelnetCommand command) |
226 | 8.26k | { |
227 | | // Check input |
228 | 8.26k | if (static_cast<int>(command) < 0) |
229 | 2.22k | { |
230 | 2.22k | PCPP_LOG_ERROR("Command type can't be negative"); |
231 | 2.22k | return TelnetOption::TelnetOptionNoOption; |
232 | 2.22k | } |
233 | | |
234 | 6.03k | if (isCommandField(m_Data) && m_Data[1] == static_cast<int>(command)) |
235 | 1.09k | return static_cast<TelnetOption>(getSubCommand(m_Data, getFieldLen(m_Data, m_DataLen))); |
236 | | |
237 | 4.94k | uint8_t* pos = m_Data; |
238 | 9.52k | while (pos != nullptr) |
239 | 9.52k | { |
240 | 9.52k | size_t offset = pos - m_Data; |
241 | 9.52k | pos = getNextCommandField(pos, m_DataLen - offset); |
242 | | |
243 | 9.52k | if (pos && pos[1] == static_cast<int>(command)) |
244 | 4.94k | return static_cast<TelnetOption>(getSubCommand(pos, getFieldLen(pos, m_DataLen - offset))); |
245 | 9.52k | } |
246 | | |
247 | 0 | PCPP_LOG_DEBUG("Can't find requested command"); |
248 | 0 | return TelnetOption::TelnetOptionNoOption; |
249 | 4.94k | } |
250 | | |
251 | | uint8_t* TelnetLayer::getOptionData(size_t& length) |
252 | 8.26k | { |
253 | 8.26k | if (lastPositionOffset < m_DataLen) |
254 | 5.94k | { |
255 | 5.94k | size_t lenBuffer = getFieldLen(&m_Data[lastPositionOffset], m_DataLen - lastPositionOffset); |
256 | 5.94k | uint8_t* posBuffer = getCommandData(&m_Data[lastPositionOffset], lenBuffer); |
257 | | |
258 | 5.94k | length = lenBuffer; |
259 | 5.94k | return posBuffer; |
260 | 5.94k | } |
261 | 2.32k | return nullptr; |
262 | 8.26k | } |
263 | | |
264 | | uint8_t* TelnetLayer::getOptionData(TelnetCommand command, size_t& length) |
265 | 8.26k | { |
266 | | // Check input |
267 | 8.26k | if (static_cast<int>(command) < 0) |
268 | 2.22k | { |
269 | 2.22k | PCPP_LOG_ERROR("Command type can't be negative"); |
270 | 2.22k | length = 0; |
271 | 2.22k | return nullptr; |
272 | 2.22k | } |
273 | | |
274 | 6.03k | if (isCommandField(m_Data) && m_Data[1] == static_cast<int>(command)) |
275 | 1.09k | { |
276 | 1.09k | size_t lenBuffer = getFieldLen(m_Data, m_DataLen); |
277 | 1.09k | uint8_t* posBuffer = getCommandData(m_Data, lenBuffer); |
278 | | |
279 | 1.09k | length = lenBuffer; |
280 | 1.09k | return posBuffer; |
281 | 1.09k | } |
282 | | |
283 | 4.94k | uint8_t* pos = m_Data; |
284 | 9.52k | while (pos != nullptr) |
285 | 9.52k | { |
286 | 9.52k | size_t offset = pos - m_Data; |
287 | 9.52k | pos = getNextCommandField(pos, m_DataLen - offset); |
288 | | |
289 | 9.52k | if (pos && pos[1] == static_cast<int>(command)) |
290 | 4.94k | { |
291 | 4.94k | size_t lenBuffer = getFieldLen(m_Data, m_DataLen); |
292 | 4.94k | uint8_t* posBuffer = getCommandData(m_Data, lenBuffer); |
293 | | |
294 | 4.94k | length = lenBuffer; |
295 | 4.94k | return posBuffer; |
296 | 4.94k | } |
297 | 9.52k | } |
298 | | |
299 | 0 | PCPP_LOG_DEBUG("Can't find requested command"); |
300 | 0 | length = 0; |
301 | 0 | return nullptr; |
302 | 4.94k | } |
303 | | |
304 | | std::string TelnetLayer::getTelnetCommandAsString(TelnetCommand val) |
305 | 8.26k | { |
306 | 8.26k | switch (val) |
307 | 8.26k | { |
308 | 2.22k | case TelnetCommand::TelnetCommandEndOfPacket: |
309 | 2.22k | return "Reached end of packet while parsing"; |
310 | 36 | case TelnetCommand::EndOfFile: |
311 | 36 | return "End of File"; |
312 | 0 | case TelnetCommand::Suspend: |
313 | 0 | return "Suspend current process"; |
314 | 68 | case TelnetCommand::Abort: |
315 | 68 | return "Abort Process"; |
316 | 146 | case TelnetCommand::EndOfRecordCommand: |
317 | 146 | return "End of Record"; |
318 | 880 | case TelnetCommand::SubnegotiationEnd: |
319 | 880 | return "Subnegotiation End"; |
320 | 4 | case TelnetCommand::NoOperation: |
321 | 4 | return "No Operation"; |
322 | 48 | case TelnetCommand::DataMark: |
323 | 48 | return "Data Mark"; |
324 | 58 | case TelnetCommand::Break: |
325 | 58 | return "Break"; |
326 | 387 | case TelnetCommand::InterruptProcess: |
327 | 387 | return "Interrupt Process"; |
328 | 22 | case TelnetCommand::AbortOutput: |
329 | 22 | return "Abort Output"; |
330 | 1 | case TelnetCommand::AreYouThere: |
331 | 1 | return "Are You There"; |
332 | 161 | case TelnetCommand::EraseCharacter: |
333 | 161 | return "Erase Character"; |
334 | 32 | case TelnetCommand::EraseLine: |
335 | 32 | return "Erase Line"; |
336 | 174 | case TelnetCommand::GoAhead: |
337 | 174 | return "Go Ahead"; |
338 | 962 | case TelnetCommand::Subnegotiation: |
339 | 962 | return "Subnegotiation"; |
340 | 64 | case TelnetCommand::WillPerform: |
341 | 64 | return "Will Perform"; |
342 | 192 | case TelnetCommand::WontPerform: |
343 | 192 | return "Wont Perform"; |
344 | 128 | case TelnetCommand::DoPerform: |
345 | 128 | return "Do Perform"; |
346 | 427 | case TelnetCommand::DontPerform: |
347 | 427 | return "Dont Perform"; |
348 | 0 | case TelnetCommand::InterpretAsCommand: |
349 | 0 | return "Interpret As Command"; |
350 | 2.24k | default: |
351 | 2.24k | return "Unknown Command"; |
352 | 8.26k | } |
353 | 8.26k | } |
354 | | |
355 | | std::string TelnetLayer::getTelnetOptionAsString(TelnetOption val) |
356 | 8.26k | { |
357 | 8.26k | switch (val) |
358 | 8.26k | { |
359 | 6.83k | case TelnetOption::TelnetOptionNoOption: |
360 | 6.83k | return "No option for this command"; |
361 | 0 | case TelnetOption::TransmitBinary: |
362 | 0 | return "Binary Transmission"; |
363 | 0 | case TelnetOption::Echo: |
364 | 0 | return "Echo"; |
365 | 0 | case TelnetOption::Reconnection: |
366 | 0 | return "Reconnection"; |
367 | 0 | case TelnetOption::SuppressGoAhead: |
368 | 0 | return "Suppress Go Ahead"; |
369 | 0 | case TelnetOption::ApproxMsgSizeNegotiation: |
370 | 0 | return "Negotiate approximate message size"; |
371 | 0 | case TelnetOption::Status: |
372 | 0 | return "Status"; |
373 | 0 | case TelnetOption::TimingMark: |
374 | 0 | return "Timing Mark"; |
375 | 32 | case TelnetOption::RemoteControlledTransAndEcho: |
376 | 32 | return "Remote Controlled Transmission and Echo"; |
377 | 0 | case TelnetOption::OutputLineWidth: |
378 | 0 | return "Output Line Width"; |
379 | 0 | case TelnetOption::OutputPageSize: |
380 | 0 | return "Output Page Size"; |
381 | 0 | case TelnetOption::OutputCarriageReturnDisposition: |
382 | 0 | return "Negotiate About Output Carriage-Return Disposition"; |
383 | 0 | case TelnetOption::OutputHorizontalTabStops: |
384 | 0 | return "Negotiate About Output Horizontal Tabstops"; |
385 | 0 | case TelnetOption::OutputHorizontalTabDisposition: |
386 | 0 | return "Negotiate About Output Horizontal Tab Disposition"; |
387 | 0 | case TelnetOption::OutputFormfeedDisposition: |
388 | 0 | return "Negotiate About Output Formfeed Disposition"; |
389 | 0 | case TelnetOption::OutputVerticalTabStops: |
390 | 0 | return "Negotiate About Vertical Tabstops"; |
391 | 0 | case TelnetOption::OutputVerticalTabDisposition: |
392 | 0 | return "Negotiate About Output Vertcial Tab Disposition"; |
393 | 0 | case TelnetOption::OutputLinefeedDisposition: |
394 | 0 | return "Negotiate About Output Linefeed Disposition"; |
395 | 0 | case TelnetOption::ExtendedASCII: |
396 | 0 | return "Extended ASCII"; |
397 | 24 | case TelnetOption::Logout: |
398 | 24 | return "Logout"; |
399 | 32 | case TelnetOption::ByteMacro: |
400 | 32 | return "Byte Macro"; |
401 | 0 | case TelnetOption::DataEntryTerminal: |
402 | 0 | return "Data Entry Terminal"; |
403 | 0 | case TelnetOption::SUPDUP: |
404 | 0 | return "SUPDUP"; |
405 | 32 | case TelnetOption::SUPDUPOutput: |
406 | 32 | return "SUPDUP Output"; |
407 | 32 | case TelnetOption::SendLocation: |
408 | 32 | return "Send Location"; |
409 | 0 | case TelnetOption::TerminalType: |
410 | 0 | return "Terminal Type"; |
411 | 0 | case TelnetOption::EndOfRecordOption: |
412 | 0 | return "End Of Record"; |
413 | 0 | case TelnetOption::TACACSUserIdentification: |
414 | 0 | return "TACACS User Identification"; |
415 | 0 | case TelnetOption::OutputMarking: |
416 | 0 | return "Output Marking"; |
417 | 0 | case TelnetOption::TerminalLocationNumber: |
418 | 0 | return "Terminal Location Number"; |
419 | 2 | case TelnetOption::Telnet3270Regime: |
420 | 2 | return "Telnet 3270 Regime"; |
421 | 24 | case TelnetOption::X3Pad: |
422 | 24 | return "X3 Pad"; |
423 | 0 | case TelnetOption::NegotiateAboutWindowSize: |
424 | 0 | return "Negotiate About Window Size"; |
425 | 96 | case TelnetOption::TerminalSpeed: |
426 | 96 | return "Terminal Speed"; |
427 | 96 | case TelnetOption::RemoteFlowControl: |
428 | 96 | return "Remote Flow Control"; |
429 | 0 | case TelnetOption::Linemode: |
430 | 0 | return "Line mode"; |
431 | 0 | case TelnetOption::XDisplayLocation: |
432 | 0 | return "X Display Location"; |
433 | 0 | case TelnetOption::EnvironmentOption: |
434 | 0 | return "Environment Option"; |
435 | 65 | case TelnetOption::AuthenticationOption: |
436 | 65 | return "Authentication Option"; |
437 | 128 | case TelnetOption::EncryptionOption: |
438 | 128 | return "Encryption Option"; |
439 | 0 | case TelnetOption::NewEnvironmentOption: |
440 | 0 | return "New Environment Option"; |
441 | 0 | case TelnetOption::TN3270E: |
442 | 0 | return "TN3270E"; |
443 | 0 | case TelnetOption::XAuth: |
444 | 0 | return "X Server Authentication"; |
445 | 0 | case TelnetOption::Charset: |
446 | 0 | return "Charset"; |
447 | 0 | case TelnetOption::TelnetRemoteSerialPort: |
448 | 0 | return "Telnet Remote Serial Port"; |
449 | 22 | case TelnetOption::ComPortControlOption: |
450 | 22 | return "Com Port Control Option"; |
451 | 0 | case TelnetOption::TelnetSuppressLocalEcho: |
452 | 0 | return "Telnet Suppress Local Echo"; |
453 | 0 | case TelnetOption::TelnetStartTLS: |
454 | 0 | return "Telnet Start TLS"; |
455 | 0 | case TelnetOption::Kermit: |
456 | 0 | return "Kermit"; |
457 | 0 | case TelnetOption::SendURL: |
458 | 0 | return "Send URL"; |
459 | 0 | case TelnetOption::ForwardX: |
460 | 0 | return "Forward X Server"; |
461 | 0 | case TelnetOption::TelOptPragmaLogon: |
462 | 0 | return "Telnet Option Pragma Logon"; |
463 | 130 | case TelnetOption::TelOptSSPILogon: |
464 | 130 | return "Telnet Option SSPI Logon"; |
465 | 33 | case TelnetOption::TelOptPragmaHeartbeat: |
466 | 33 | return "Telnet Option Pragma Heartbeat"; |
467 | 655 | case TelnetOption::ExtendedOptions: |
468 | 655 | return "Extended option list"; |
469 | 26 | default: |
470 | 26 | return "Unknown Option"; |
471 | 8.26k | } |
472 | 8.26k | } |
473 | | |
474 | | std::string TelnetLayer::toString() const |
475 | 4.45k | { |
476 | 4.45k | if (isDataField(m_Data)) |
477 | 2.64k | return "Telnet Data"; |
478 | 1.81k | return "Telnet Control"; |
479 | 4.45k | } |
480 | | |
481 | | } // namespace pcpp |