2 * Routines for UDP RDP packet dissection
3 * Copyright 2021, David Fort
5 * Wireshark - Network traffic analyzer
6 * By Gerald Combs <gerald@wireshark.org>
7 * Copyright 1998 Gerald Combs
9 * SPDX-License-Identifier: GPL-2.0-or-later
14 #include <epan/packet.h>
15 #include <epan/prefs.h>
16 #include <epan/expert.h>
17 #include <epan/conversation.h>
18 #include <epan/proto_data.h>
20 #include "packet-rdp.h"
21 #include "packet-rdpudp.h"
23 #define PNAME "UDP Remote Desktop Protocol"
24 #define PSNAME "RDPUDP"
25 #define PFNAME "rdpudp"
27 void proto_register_rdpudp(void);
28 void proto_reg_handoff_rdpudp(void);
30 static dissector_handle_t rdpudp_handle
;
33 static int ett_rdpudp
;
34 static int ett_rdpudp_flags
;
35 static int ett_rdpudp_synex
;
36 static int ett_rdpudp_ack
;
37 static int ett_rdpudp_fec
;
38 static int ett_rdpudp_data
;
39 static int ett_rdpudp2_packetType
;
40 static int ett_rdpudp2_flags
;
41 static int ett_rdpudp2_ack
;
42 static int ett_rdpudp2_overhead
;
43 static int ett_rdpudp2_delayack
;
44 static int ett_rdpudp2_aoa
;
45 static int ett_rdpudp2_data
;
46 static int ett_rdpudp2_ackvec
;
47 static int ett_rdpudp2_ackvec_vecs
;
48 static int ett_rdpudp2_ackvec_vec
;
51 static int hf_rdpudp_snSourceAck
;
52 static int hf_rdpudp_ReceiveWindowSize
;
53 static int hf_rdpudp_flags
;
54 static int hf_rdpudp_flag_syn
;
55 static int hf_rdpudp_flag_fin
;
56 static int hf_rdpudp_flag_ack
;
57 static int hf_rdpudp_flag_data
;
58 static int hf_rdpudp_flag_fec
;
59 static int hf_rdpudp_flag_cn
;
60 static int hf_rdpudp_flag_cwr
;
61 static int hf_rdpudp_flag_aoa
;
62 static int hf_rdpudp_flag_synlossy
;
63 static int hf_rdpudp_flag_ackdelayed
;
64 static int hf_rdpudp_flag_correlationId
;
65 static int hf_rdpudp_flag_synex
;
66 static int hf_rdpudp_snInitialSequenceNumber
;
67 static int hf_rdpudp_upstreamMtu
;
68 static int hf_rdpudp_downstreamMtu
;
69 static int hf_rdpudp_correlationId
;
70 static int hf_rdpudp_synex_flags
;
71 static int hf_rdpudp_synex_flag_version
;
72 static int hf_rdpudp_synex_version
;
73 static int hf_rdpudp_synex_cookiehash
;
74 static int hf_rdpudp_ack_vectorsize
;
75 static int hf_rdpudp_ack_item
;
76 static int hf_rdpudp_ack_item_state
;
77 static int hf_rdpudp_ack_item_rle
;
78 static int hf_rdpudp_fec_coded
;
79 static int hf_rdpudp_fec_sourcestart
;
80 static int hf_rdpudp_fec_range
;
81 static int hf_rdpudp_fec_fecindex
;
82 static int hf_rdpudp_resetseqenum
;
83 static int hf_rdpudp_source_sncoded
;
84 static int hf_rdpudp_source_snSourceStart
;
85 static int hf_rdpudp_data
;
87 static int * const rdpudp_flags
[] = {
96 &hf_rdpudp_flag_synlossy
,
97 &hf_rdpudp_flag_ackdelayed
,
98 &hf_rdpudp_flag_correlationId
,
99 &hf_rdpudp_flag_synex
,
103 static int hf_rdpudp2_PacketPrefixByte
;
104 static int hf_rdpudp2_packetType
;
105 static int hf_rdpudp2_shortPacketLength
;
106 static int hf_rdpudp2_flags
;
107 static int hf_rdpudp2_flag_ack
;
108 static int hf_rdpudp2_flag_data
;
109 static int hf_rdpudp2_flag_ackvec
;
110 static int hf_rdpudp2_flag_aoa
;
111 static int hf_rdpudp2_flag_overhead
;
112 static int hf_rdpudp2_flag_delayackinfo
;
113 static int hf_rdpudp2_logWindow
;
114 static int hf_rdpudp2_AckSeq
;
115 static int hf_rdpudp2_AckTs
;
116 static int hf_rdpudp2_AckSendTimeGap
;
117 static int hf_rdpudp2_ndelayedAcks
;
118 static int hf_rdpudp2_delayedTimeScale
;
119 static int hf_rdpudp2_delayedAcks
;
120 static int hf_rdpudp2_delayedAck
;
121 static int hf_rdpudp2_OverHeadSize
;
122 static int hf_rdpudp2_DelayAckMax
;
123 static int hf_rdpudp2_DelayAckTimeout
;
124 static int hf_rdpudp2_AckOfAcksSeqNum
;
125 static int hf_rdpudp2_DataSeqNumber
;
126 static int hf_rdpudp2_DataFullSeqNumber
;
127 static int hf_rdpudp2_DataChannelSeqNumber
;
128 static int hf_rdpudp2_DataChannelFullSeqNumber
;
129 static int hf_rdpudp2_Data
;
130 static int hf_rdpudp2_AckvecBaseSeq
;
131 static int hf_rdpudp2_AckvecCodecAckVecSize
;
132 static int hf_rdpudp2_AckvecHaveTs
;
133 static int hf_rdpudp2_AckvecTimeStamp
;
134 static int hf_rdpudp2_SendAckTimeGapInMs
;
135 static int hf_rdpudp2_AckvecCodedAck
;
136 static int hf_rdpudp2_AckvecCodedAckMode
;
137 static int hf_rdpudp2_AckvecCodedAckRleState
;
138 static int hf_rdpudp2_AckvecCodedAckRleLen
;
140 static int * const rdpudp2_flags
[] = {
141 &hf_rdpudp2_flag_ack
,
142 &hf_rdpudp2_flag_data
,
143 &hf_rdpudp2_flag_ackvec
,
144 &hf_rdpudp2_flag_aoa
,
145 &hf_rdpudp2_flag_overhead
,
146 &hf_rdpudp2_flag_delayackinfo
,
147 &hf_rdpudp2_logWindow
,
151 static dissector_handle_t tls_handle
;
152 static dissector_handle_t dtls_handle
;
155 RDPUDP_FULL_DATA_SEQ_KEY
= 1,
156 RDPUDP_FULL_CHANNEL_SEQ_KEY
= 2
163 RDPUDP_DATA
= 0x0008,
168 RDPUDP_SYNLOSSY
= 0x0200,
169 RDPUDP_ACKDELAYED
= 0x0400,
170 RDPUDP_CORRELATIONID
= 0x0800,
171 RDPUDP_SYNEX
= 0x1000
174 #define RDPUDP_VERSION_INFO_VALID 0x0001
177 RDPUDP2_ACK
= 0x0001,
178 RDPUDP2_DATA
= 0x0004,
179 RDPUDP2_ACKVEC
= 0x0008,
180 RDPUDP2_AOA
= 0x0010,
181 RDPUDP2_OVERHEAD
= 0x0040,
182 RDPUDP2_DELAYACK
= 0x0100
185 static const value_string rdpudp_version_vals
[] = {
186 { 0x0001, "UDPv1-1" },
187 { 0x0002, "UDPv1-2" },
192 static const value_string rdpudp_ack_states_vals
[] = {
200 static const value_string rdpudp2_packetType_vals
[] = {
206 static const value_string rdpudp2_ackvec_mode_vals
[] = {
208 { 0x01, "Run length"},
212 static const value_string rdpudp2_ackvec_rlestates_vals
[] = {
214 { 0x01, "received" },
219 rdpudp_chunk_free_cb(const void *key _U_
, void *value
, void *userdata _U_
)
221 tvbuff_t
*tvb
= (tvbuff_t
*)value
;
228 rdpudp_info_free_cb(wmem_allocator_t
*allocator _U_
, wmem_cb_event_t event _U_
,
231 rdpudp_conv_info_t
*rdpudp_info
= (rdpudp_conv_info_t
*)user_data
;
233 wmem_tree_foreach(rdpudp_info
->client_chunks
, rdpudp_chunk_free_cb
, NULL
);
234 wmem_tree_foreach(rdpudp_info
->server_chunks
, rdpudp_chunk_free_cb
, NULL
);
240 rdp_isServerAddressTarget(packet_info
*pinfo
)
242 conversation_t
*conv
;
243 rdp_conv_info_t
*rdp_info
;
244 rdpudp_conv_info_t
*rdpudp_info
;
246 conv
= find_conversation_pinfo(pinfo
, 0);
250 rdp_info
= (rdp_conv_info_t
*)conversation_get_proto_data(conv
, proto_rdp
);
252 rdp_server_address_t
*server
= &rdp_info
->serverAddr
;
253 return addresses_equal(&server
->addr
, &pinfo
->dst
) && (pinfo
->destport
== server
->port
);
256 rdpudp_info
= (rdpudp_conv_info_t
*)conversation_get_proto_data(conv
, proto_rdpudp
);
260 return addresses_equal(&rdpudp_info
->server_addr
, &pinfo
->dst
) && (rdpudp_info
->server_port
== pinfo
->destport
);
264 rdpudp_is_reliable_transport(packet_info
*pinfo
)
266 conversation_t
*conv
;
267 rdpudp_conv_info_t
*rdpudp_info
;
269 conv
= find_conversation_pinfo(pinfo
, 0);
273 rdpudp_info
= (rdpudp_conv_info_t
*)conversation_get_proto_data(conv
, proto_rdpudp
);
277 return !rdpudp_info
->is_lossy
;
281 dissect_rdpudp_v1(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, rdpudp_conv_info_t
*conv
)
286 col_set_str(pinfo
->cinfo
, COL_PROTOCOL
, "RDPUDP");
287 col_clear(pinfo
->cinfo
, COL_INFO
);
289 proto_tree_add_item(tree
, hf_rdpudp_snSourceAck
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
292 proto_tree_add_item(tree
, hf_rdpudp_ReceiveWindowSize
, tvb
, offset
, 2, ENC_BIG_ENDIAN
);
295 proto_tree_add_bitmask(tree
, tvb
, offset
, hf_rdpudp_flags
, ett_rdpudp_flags
, rdpudp_flags
, ENC_BIG_ENDIAN
);
296 flags
= tvb_get_uint16(tvb
, offset
, ENC_BIG_ENDIAN
);
299 if (flags
& RDPUDP_SYN
) {
300 conv
->is_lossy
= (flags
& RDPUDP_SYNLOSSY
);
301 if (!(flags
& RDPUDP_ACK
)) {
302 /* set the server address only on the first SYN packet */
303 copy_address_wmem(wmem_file_scope(), &conv
->server_addr
, &pinfo
->dst
);
304 conv
->server_port
= pinfo
->destport
;
306 col_append_sep_str(pinfo
->cinfo
, COL_INFO
, ",", "SYN");
309 if (flags
& RDPUDP_SYN
) {
310 proto_tree_add_item(tree
, hf_rdpudp_snInitialSequenceNumber
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
312 proto_tree_add_item(tree
, hf_rdpudp_upstreamMtu
, tvb
, offset
, 2, ENC_BIG_ENDIAN
);
314 proto_tree_add_item(tree
, hf_rdpudp_downstreamMtu
, tvb
, offset
, 2, ENC_BIG_ENDIAN
);
318 if (flags
& RDPUDP_CORRELATIONID
) {
319 proto_tree_add_item(tree
, hf_rdpudp_correlationId
, tvb
, offset
, 16, ENC_NA
);
321 col_append_sep_str(pinfo
->cinfo
, COL_INFO
, ",", "CORRELATIONID");
324 if (flags
& RDPUDP_SYNEX
) {
325 uint16_t synex_flags
;
326 proto_tree
*synex_tree
;
327 unsigned synex_sz
= 2;
328 uint16_t version_val
;
330 col_append_sep_str(pinfo
->cinfo
, COL_INFO
, ",", "SYNEX");
332 synex_flags
= tvb_get_uint16(tvb
, offset
, ENC_BIG_ENDIAN
);
333 if (synex_flags
& RDPUDP_VERSION_INFO_VALID
) {
334 synex_sz
+= 2; /* version */
336 version_val
= tvb_get_uint16(tvb
, offset
+2, ENC_BIG_ENDIAN
);
338 if (version_val
== 0x101)
339 synex_sz
+= 32; /* cookie hash */
342 synex_tree
= proto_tree_add_subtree(tree
, tvb
, offset
, synex_sz
, ett_rdpudp_synex
, NULL
, "SynEx");
343 proto_tree_add_item(synex_tree
, hf_rdpudp_synex_flags
, tvb
, offset
, 2, ENC_BIG_ENDIAN
);
344 proto_tree_add_item(synex_tree
, hf_rdpudp_synex_flag_version
, tvb
, offset
, 2, ENC_BIG_ENDIAN
);
348 if (synex_flags
& RDPUDP_VERSION_INFO_VALID
) {
349 proto_tree_add_item(synex_tree
, hf_rdpudp_synex_version
, tvb
, offset
, 2, ENC_BIG_ENDIAN
);
352 if (version_val
== 0x101) {
353 proto_tree_add_item(synex_tree
, hf_rdpudp_synex_cookiehash
, tvb
, offset
, 32, ENC_NA
);
356 if (flags
& RDPUDP_ACK
)
357 conv
->start_v2_at
= pinfo
->num
+ 1;
362 if ((flags
& RDPUDP_ACK
) && !(flags
& RDPUDP_SYN
)) {
363 proto_tree
*ack_tree
;
364 uint16_t uAckVectorSize
= tvb_get_uint16(tvb
, offset
, ENC_BIG_ENDIAN
);
366 ack_tree
= proto_tree_add_subtree(tree
, tvb
, offset
, 2 + uAckVectorSize
, ett_rdpudp_ack
, NULL
, "Ack");
368 for ( ; uAckVectorSize
; uAckVectorSize
--, offset
++) {
369 proto_tree_add_item(ack_tree
, hf_rdpudp_ack_item
, tvb
, offset
, 1, ENC_BIG_ENDIAN
);
370 proto_tree_add_item(ack_tree
, hf_rdpudp_ack_item_rle
, tvb
, offset
, 1, ENC_BIG_ENDIAN
);
372 col_append_sep_str(pinfo
->cinfo
, COL_INFO
, ",", "ACK");
375 if (flags
& RDPUDP_FEC
) {
376 proto_tree
*fec_tree
= proto_tree_add_subtree(tree
, tvb
, offset
, 4 * 3, ett_rdpudp_fec
, NULL
, "FEC");
378 proto_tree_add_item(fec_tree
, hf_rdpudp_fec_coded
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
380 proto_tree_add_item(fec_tree
, hf_rdpudp_fec_sourcestart
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
382 proto_tree_add_item(fec_tree
, hf_rdpudp_fec_range
, tvb
, offset
, 1, ENC_BIG_ENDIAN
);
384 proto_tree_add_item(fec_tree
, hf_rdpudp_fec_fecindex
, tvb
, offset
, 1, ENC_BIG_ENDIAN
);
387 col_append_sep_str(pinfo
->cinfo
, COL_INFO
, ",", "FEC");
390 if (flags
& RDPUDP_AOA
) {
391 proto_tree_add_item(tree
, hf_rdpudp_resetseqenum
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
393 col_append_sep_str(pinfo
->cinfo
, COL_INFO
, ",", "AOA");
396 if (flags
& RDPUDP_DATA
)
397 col_append_sep_str(pinfo
->cinfo
, COL_INFO
, ",", "DATA");
399 if (flags
& RDPUDP_DATA
) {
400 proto_tree
*data_tree
= proto_tree_add_subtree(tree
, tvb
, offset
, -1, ett_rdpudp_data
, NULL
, "Data");
401 dissector_handle_t target_dissector
;
403 proto_tree_add_item(data_tree
, hf_rdpudp_source_sncoded
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
406 proto_tree_add_item(data_tree
, hf_rdpudp_source_snSourceStart
, tvb
, offset
, 4, ENC_BIG_ENDIAN
);
409 target_dissector
= conv
->is_lossy
? dtls_handle
: tls_handle
;
411 call_dissector(target_dissector
, tvb_new_subset_remaining(tvb
, offset
), pinfo
, data_tree
);
413 offset
= tvb_reported_length(tvb
);
420 unwrap_udp_v2(tvbuff_t
*tvb
, packet_info
*pinfo
)
422 int len
= tvb_captured_length_remaining(tvb
, 0);
423 unsigned char *buffer
= (unsigned char*)wmem_alloc(pinfo
->pool
, len
);
425 /* copy and do the swap of byte 0 and 7*/
426 tvb_memcpy(tvb
, buffer
, 0, len
);
427 buffer
[0] = tvb_get_uint8(tvb
, 7);
428 buffer
[7] = tvb_get_uint8(tvb
, 0);
430 return tvb_new_child_real_data(tvb
, buffer
, len
, len
);
434 computeAndUpdateSeqContext(rdpudp_seq_context_t
*context
, uint16_t seq
)
436 uint16_t diff
= (context
->last_received
> seq
) ? (context
->last_received
- seq
) : (seq
- context
->last_received
);
440 /* not too much difference between last and seq, so we keep the same base
443 * [0 ...................... 0xffff]
447 if (seq
> context
->last_received
)
448 context
->last_received
= seq
;
449 return (context
->current_base
+ seq
);
452 /* when diff is bigger than 8000 that means that either we've just switched
453 * the base, or that it is a sequence number from the previous base
455 if (seq
< context
->last_received
) {
456 /* in this case we have
457 * [0 ...................... 0xffff]
461 * so the new sequence number is in fact after last_received: we've just
464 context
->last_received
= seq
;
465 context
->current_base
+= 0x10000;
466 return (context
->current_base
+ seq
);
469 /* this is a sequence number from the previous base
471 * [0 ........................ 0xffff]
475 return (context
->current_base
+ seq
- 0x10000);
479 dissect_rdpudp_v2(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, rdpudp_conv_info_t
*rdpudp
)
482 proto_tree
*subtree
, *data_tree
= NULL
;
487 tvbuff_t
*tvb2
= unwrap_udp_v2(tvb
, pinfo
);
489 col_set_str(pinfo
->cinfo
, COL_PROTOCOL
, "RDPUDP2");
490 col_clear(pinfo
->cinfo
, COL_INFO
);
492 add_new_data_source(pinfo
, tvb2
, "Unwrapped RDPUDP2 packet");
494 packet_type
= (tvb_get_uint8(tvb2
, 0) >> 1) & 0xf;
495 item
= proto_tree_add_item(tree
, hf_rdpudp2_PacketPrefixByte
, tvb2
, offset
, 1, ENC_LITTLE_ENDIAN
);
496 subtree
= proto_item_add_subtree(item
, ett_rdpudp2_packetType
);
497 proto_tree_add_item(subtree
, hf_rdpudp2_packetType
, tvb2
, offset
, 1, ENC_LITTLE_ENDIAN
);
498 proto_tree_add_item(subtree
, hf_rdpudp2_shortPacketLength
, tvb2
, offset
, 1, ENC_LITTLE_ENDIAN
);
501 proto_tree_add_bitmask(tree
, tvb2
, offset
, hf_rdpudp2_flags
, ett_rdpudp2_flags
, rdpudp2_flags
, ENC_LITTLE_ENDIAN
);
503 flags
= tvb_get_uint16(tvb2
, offset
, ENC_LITTLE_ENDIAN
);
506 if (flags
& RDPUDP2_ACK
) {
507 uint8_t nacks
= tvb_get_uint8(tvb
, offset
+ 6) & 0xf;
508 subtree
= proto_tree_add_subtree(tree
, tvb2
, offset
, 7 + nacks
, ett_rdpudp2_ack
, NULL
, "Ack");
509 proto_tree_add_item(subtree
, hf_rdpudp2_AckSeq
, tvb2
, offset
, 2, ENC_LITTLE_ENDIAN
); offset
+= 2;
510 proto_tree_add_item(subtree
, hf_rdpudp2_AckTs
, tvb2
, offset
, 3, ENC_LITTLE_ENDIAN
); offset
+= 3;
511 proto_tree_add_item(subtree
, hf_rdpudp2_AckSendTimeGap
, tvb2
, offset
, 1, ENC_LITTLE_ENDIAN
); offset
++;
513 proto_tree_add_item(subtree
, hf_rdpudp2_ndelayedAcks
, tvb2
, offset
, 1, ENC_LITTLE_ENDIAN
);
514 proto_tree_add_item(subtree
, hf_rdpudp2_delayedTimeScale
, tvb2
, offset
, 1, ENC_LITTLE_ENDIAN
);
518 col_append_sep_str(pinfo
->cinfo
, COL_INFO
, ",", "ACK");
521 if (flags
& RDPUDP2_OVERHEAD
) {
522 subtree
= proto_tree_add_subtree(tree
, tvb2
, offset
, 1, ett_rdpudp2_overhead
, NULL
, "Overhead");
523 proto_tree_add_item(subtree
, hf_rdpudp2_OverHeadSize
, tvb2
, offset
, 1, ENC_LITTLE_ENDIAN
);
525 col_append_sep_str(pinfo
->cinfo
, COL_INFO
, ",", "OVERHEAD");
529 if (flags
& RDPUDP2_DELAYACK
) {
530 subtree
= proto_tree_add_subtree(tree
, tvb2
, offset
, 3, ett_rdpudp2_delayack
, NULL
, "DelayAck");
531 proto_tree_add_item(subtree
, hf_rdpudp2_DelayAckMax
, tvb2
, offset
, 1, ENC_LITTLE_ENDIAN
);
533 proto_tree_add_item(subtree
, hf_rdpudp2_DelayAckTimeout
, tvb2
, offset
, 2, ENC_LITTLE_ENDIAN
);
536 col_append_sep_str(pinfo
->cinfo
, COL_INFO
, ",", "DELAYACK");
539 if (flags
& RDPUDP2_AOA
) {
540 subtree
= proto_tree_add_subtree(tree
, tvb2
, offset
, 1, ett_rdpudp2_aoa
, NULL
, "Ack of Acks");
541 proto_tree_add_item(subtree
, hf_rdpudp2_AckOfAcksSeqNum
, tvb2
, offset
, 2, ENC_LITTLE_ENDIAN
);
543 col_append_sep_str(pinfo
->cinfo
, COL_INFO
, ",", "AOA");
546 if (flags
& RDPUDP2_DATA
) {
549 bool is_server_target
= rdp_isServerAddressTarget(pinfo
);
550 rdpudp_seq_context_t
*target_seq_context
= is_server_target
? &rdpudp
->client_data_seq
: &rdpudp
->server_data_seq
;
552 bool isDummy
= !!(packet_type
== 0x8);
553 data_tree
= proto_tree_add_subtree(tree
, tvb2
, offset
, 1, ett_rdpudp2_data
, NULL
, isDummy
? "Dummy data" : "Data");
554 proto_tree_add_item_ret_uint(data_tree
, hf_rdpudp2_DataSeqNumber
, tvb2
, offset
, 2, ENC_LITTLE_ENDIAN
, &rawSeq
);
556 if (!PINFO_FD_VISITED(pinfo
)) {
557 seqPtr
= wmem_alloc(wmem_file_scope(), sizeof(*seqPtr
));
558 *seqPtr
= computeAndUpdateSeqContext(target_seq_context
, rawSeq
);
560 p_set_proto_data(wmem_file_scope(), pinfo
, proto_rdpudp
, RDPUDP_FULL_DATA_SEQ_KEY
, seqPtr
);
562 seqPtr
= (uint64_t *)p_get_proto_data(wmem_file_scope(), pinfo
, proto_rdpudp
, RDPUDP_FULL_DATA_SEQ_KEY
);
564 proto_item_set_generated(
565 proto_tree_add_uint(data_tree
, hf_rdpudp2_DataFullSeqNumber
, tvb2
, offset
, 2, (uint32_t)*seqPtr
)
570 col_append_sep_str(pinfo
->cinfo
, COL_INFO
, ",", isDummy
? "DUMMY" : "DATA");
573 if (flags
& RDPUDP2_ACKVEC
) {
574 proto_tree
*acks_tree
;
578 uint8_t codedAckVecSizeA
= tvb_get_uint8(tvb2
, offset
+ 2);
579 uint8_t codedAckVecSize
= codedAckVecSizeA
& 0x7f;
580 bool haveTs
= !!(codedAckVecSizeA
& 0x80);
582 ackvecSz
+= codedAckVecSize
;
586 subtree
= proto_tree_add_subtree(tree
, tvb2
, offset
, ackvecSz
, ett_rdpudp2_ackvec
, NULL
, "AckVec");
587 proto_tree_add_item_ret_uint(subtree
, hf_rdpudp2_AckvecBaseSeq
, tvb2
, offset
, 2, ENC_LITTLE_ENDIAN
, &base_seq
);
590 proto_tree_add_item(subtree
, hf_rdpudp2_AckvecCodecAckVecSize
, tvb2
, offset
, 1, ENC_LITTLE_ENDIAN
);
591 proto_tree_add_item(subtree
, hf_rdpudp2_AckvecHaveTs
, tvb2
, offset
, 1, ENC_LITTLE_ENDIAN
);
595 proto_tree_add_item(subtree
, hf_rdpudp2_AckvecTimeStamp
, tvb2
, offset
, 3, ENC_LITTLE_ENDIAN
);
598 proto_tree_add_item(subtree
, hf_rdpudp2_SendAckTimeGapInMs
, tvb2
, offset
, 1, ENC_LITTLE_ENDIAN
);
602 acks_tree
= proto_tree_add_subtree(subtree
, tvb2
, offset
, codedAckVecSize
, ett_rdpudp2_ackvec_vecs
, NULL
, "Acks");
603 for (i
= 0; i
< codedAckVecSize
; i
++) {
604 proto_tree
*ack_tree
;
606 uint8_t b
= tvb_get_uint8(tvb2
, offset
+ i
);
609 /* run length mode */
610 uint8_t rle_len
= (b
& 0x3f);
611 ack_tree
= proto_tree_add_subtree_format(acks_tree
, tvb2
, offset
+ i
, 1, ett_rdpudp2_ackvec_vec
, NULL
,
612 "RLE %s %04x -> %04x", (b
& 0x40) ? "received" : "lost",
613 base_seq
, base_seq
+ rle_len
);
618 ack_tree
= proto_tree_add_subtree_format(acks_tree
, tvb2
, offset
+ i
, 1, ett_rdpudp2_ackvec_vec
, NULL
,
619 "bitmap %s%04x %s%04x %s%04x %s%04x %s%04x %s%04x %s%04x",
620 (b
& 0x01) ? "" : "!", base_seq
,
621 (b
& 0x02) ? "" : "!", base_seq
+ 1,
622 (b
& 0x04) ? "" : "!", base_seq
+ 2,
623 (b
& 0x08) ? "" : "!", base_seq
+ 3,
624 (b
& 0x10) ? "" : "!", base_seq
+ 4,
625 (b
& 0x20) ? "" : "!", base_seq
+ 5,
626 (b
& 0x40) ? "" : "!", base_seq
+ 6
631 proto_tree_add_item(ack_tree
, hf_rdpudp2_AckvecCodedAckMode
, tvb2
, offset
+ i
, 1, ENC_LITTLE_ENDIAN
);
633 proto_tree_add_item(ack_tree
, hf_rdpudp2_AckvecCodedAckRleState
, tvb2
, offset
+ i
, 1, ENC_LITTLE_ENDIAN
);
634 proto_tree_add_item(ack_tree
, hf_rdpudp2_AckvecCodedAckRleLen
, tvb2
, offset
+ i
, 1, ENC_LITTLE_ENDIAN
);
638 offset
+= codedAckVecSize
;
639 col_append_sep_str(pinfo
->cinfo
, COL_INFO
, ",", "ACKVEC");
642 if ((flags
& RDPUDP2_DATA
) && (packet_type
!= 0x8)) {
647 bool is_server_target
= rdp_isServerAddressTarget(pinfo
);
648 wmem_tree_t
*targetTree
= is_server_target
? rdpudp
->client_chunks
: rdpudp
->server_chunks
;
649 rdpudp_seq_context_t
*target_seq_context
= is_server_target
? &rdpudp
->client_channel_seq
: &rdpudp
->server_channel_seq
;
651 proto_tree_add_item_ret_uint(data_tree
, hf_rdpudp2_DataChannelSeqNumber
, tvb2
, offset
, 2, ENC_LITTLE_ENDIAN
, &rawSeq
);
652 if (!PINFO_FD_VISITED(pinfo
)) {
653 seqPtr
= wmem_alloc(wmem_file_scope(), sizeof(*seqPtr
));
654 *seqPtr
= computeAndUpdateSeqContext(target_seq_context
, rawSeq
);
656 p_set_proto_data(wmem_file_scope(), pinfo
, proto_rdpudp
, RDPUDP_FULL_CHANNEL_SEQ_KEY
, seqPtr
);
658 seqPtr
= (uint64_t *)p_get_proto_data(wmem_file_scope(), pinfo
, proto_rdpudp
, RDPUDP_FULL_CHANNEL_SEQ_KEY
);
660 proto_item_set_generated(
661 proto_tree_add_uint(data_tree
, hf_rdpudp2_DataChannelFullSeqNumber
, tvb2
, offset
, 2, (uint32_t)*seqPtr
)
665 chunk
= wmem_tree_lookup32(targetTree
, (uint32_t)*seqPtr
);
666 data_tvb
= tvb_new_composite();
669 tvb_composite_prepend(data_tvb
, chunk
);
671 subtvb
= tvb_new_subset_length(tvb2
, offset
, tvb_captured_length_remaining(tvb2
, offset
));
672 tvb_composite_append(data_tvb
, subtvb
);
673 tvb_composite_finalize(data_tvb
);
675 add_new_data_source(pinfo
, data_tvb
, "SSL fragment");
676 pinfo
->can_desegment
= 2;
678 call_dissector(tls_handle
, data_tvb
, pinfo
, data_tree
);
680 if (!PINFO_FD_VISITED(pinfo
) && pinfo
->desegment_len
) {
681 int remaining
= tvb_captured_length_remaining(subtvb
, pinfo
->desegment_offset
);
682 /* Something went wrong if seqPtr didn't advance.
683 * XXX: Should we ignore this or free the old chunk and
686 chunk
= (tvbuff_t
*)wmem_tree_lookup32(targetTree
, (uint32_t)(*seqPtr
+ 1));
690 chunk
= tvb_clone_offset_len(data_tvb
, pinfo
->desegment_offset
, remaining
);
691 wmem_tree_insert32(targetTree
, (uint32_t)(*seqPtr
+ 1), chunk
);
694 offset
= tvb_captured_length(tvb2
);
701 dissect_rdpudp(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*parent_tree
, void *data _U_
)
705 conversation_t
*conversation
;
706 rdpudp_conv_info_t
*rdpudp_info
;
708 conversation
= find_or_create_conversation(pinfo
);
710 rdpudp_info
= (rdpudp_conv_info_t
*)conversation_get_proto_data(conversation
, proto_rdpudp
);
711 if (rdpudp_info
== NULL
) {
712 rdpudp_info
= wmem_new0(wmem_file_scope(), rdpudp_conv_info_t
);
713 rdpudp_info
->start_v2_at
= UINT32_MAX
;
714 rdpudp_info
->is_lossy
= false;
715 rdpudp_info
->client_chunks
= wmem_tree_new(wmem_file_scope());
716 rdpudp_info
->server_chunks
= wmem_tree_new(wmem_file_scope());
717 wmem_register_callback(wmem_file_scope(), rdpudp_info_free_cb
, rdpudp_info
);
719 conversation_add_proto_data(conversation
, proto_rdpudp
, rdpudp_info
);
722 item
= proto_tree_add_item(parent_tree
, proto_rdpudp
, tvb
, 0, -1, ENC_NA
);
723 tree
= proto_item_add_subtree(item
, ett_rdpudp
);
725 if (rdpudp_info
->start_v2_at
> pinfo
->num
)
726 return dissect_rdpudp_v1(tvb
, pinfo
, tree
, rdpudp_info
);
728 return dissect_rdpudp_v2(tvb
, pinfo
, tree
, rdpudp_info
);
731 /*--- proto_register_rdpudp -------------------------------------------*/
733 proto_register_rdpudp(void) {
735 static hf_register_info hf
[] = {
736 { &hf_rdpudp_snSourceAck
,
737 {"snSourceAck", "rdpudp.snsourceack", FT_UINT32
, BASE_HEX
, NULL
, 0, NULL
, HFILL
},
739 { &hf_rdpudp_ReceiveWindowSize
,
740 {"ReceiveWindowSize", "rdpudp.receivewindowsize", FT_UINT16
, BASE_DEC
, NULL
, 0, NULL
, HFILL
}
743 {"Flags", "rdpudp.flags", FT_UINT16
, BASE_HEX
, NULL
, 0, NULL
, HFILL
}
745 { &hf_rdpudp_flag_syn
,
746 {"Syn", "rdpudp.flags.syn", FT_BOOLEAN
, 16, NULL
, RDPUDP_SYN
, NULL
, HFILL
}
748 { &hf_rdpudp_flag_fin
,
749 {"Fin", "rdpudp.flags.fin", FT_BOOLEAN
, 16, NULL
, RDPUDP_FIN
, NULL
, HFILL
}
751 { &hf_rdpudp_flag_ack
,
752 {"Ack", "rdpudp.flags.ack", FT_BOOLEAN
, 16, NULL
, RDPUDP_ACK
, NULL
, HFILL
}
754 { &hf_rdpudp_flag_data
,
755 {"Data", "rdpudp.flags.data", FT_BOOLEAN
, 16, NULL
, RDPUDP_DATA
, NULL
, HFILL
}
757 { &hf_rdpudp_flag_fec
,
758 {"FECData", "rdpudp.flags.fec", FT_BOOLEAN
, 16, NULL
, RDPUDP_FEC
, NULL
, HFILL
}
760 { &hf_rdpudp_flag_cn
,
761 {"CN", "rdpudp.flags.cn", FT_BOOLEAN
, 16, NULL
, RDPUDP_CN
, NULL
, HFILL
}
763 { &hf_rdpudp_flag_cwr
,
764 {"CWR", "rdpudp.flags.cwr", FT_BOOLEAN
, 16, NULL
, RDPUDP_CWR
, NULL
, HFILL
}
766 { &hf_rdpudp_flag_aoa
,
767 {"Ack of Acks", "rdpudp.flags.aoa", FT_BOOLEAN
, 16, NULL
, RDPUDP_AOA
, NULL
, HFILL
}
769 { &hf_rdpudp_flag_synlossy
,
770 {"Syn lossy", "rdpudp.flags.synlossy", FT_BOOLEAN
, 16, NULL
, RDPUDP_SYNLOSSY
, NULL
, HFILL
}
772 { &hf_rdpudp_flag_ackdelayed
,
773 {"Ack delayed", "rdpudp.flags.ackdelayed", FT_BOOLEAN
, 16, NULL
, RDPUDP_ACKDELAYED
, NULL
, HFILL
}
775 { &hf_rdpudp_flag_correlationId
,
776 {"Correlation id", "rdpudp.flags.correlationid", FT_BOOLEAN
, 16, NULL
, RDPUDP_CORRELATIONID
, NULL
, HFILL
}
778 { &hf_rdpudp_flag_synex
,
779 {"SynEx","rdpudp.flags.synex",FT_BOOLEAN
,16,NULL
,RDPUDP_SYNEX
,NULL
,HFILL
}
781 { &hf_rdpudp_snInitialSequenceNumber
,
782 {"Initial SequenceNumber","rdpudp.initialsequencenumber", FT_UINT32
, BASE_HEX
, NULL
, 0, NULL
, HFILL
}
784 { &hf_rdpudp_upstreamMtu
,
785 {"Upstream MTU", "rdpudp.upstreammtu", FT_UINT16
, BASE_DEC
, NULL
, 0, NULL
, HFILL
}
787 { &hf_rdpudp_downstreamMtu
,
788 {"DownStream MTU", "rdpudp.downstreammtu", FT_UINT16
, BASE_DEC
, NULL
, 0, NULL
, HFILL
}
790 { &hf_rdpudp_correlationId
,
791 {"Correlation Id", "rdpudp.correlationid", FT_BYTES
, BASE_NONE
, NULL
, 0, NULL
, HFILL
}
793 { &hf_rdpudp_synex_flags
,
794 {"Flags", "rdpudp.synex.flags", FT_UINT16
, BASE_HEX
, NULL
, 0, NULL
, HFILL
}
796 { &hf_rdpudp_synex_flag_version
,
797 {"Version info", "rdpudp.synex.flags.versioninfo", FT_BOOLEAN
, 16, NULL
, 0x0001, NULL
, HFILL
}
799 { &hf_rdpudp_synex_version
,
800 {"Version", "rdpudp.synex.version", FT_UINT16
, BASE_HEX
, VALS(rdpudp_version_vals
), 0, NULL
, HFILL
}
802 {&hf_rdpudp_synex_cookiehash
,
803 {"Cookie Hash", "rdpudp.synex.cookiehash", FT_BYTES
, BASE_NONE
, NULL
, 0, NULL
, HFILL
}
805 { &hf_rdpudp_ack_vectorsize
,
806 {"uAckVectorSize", "rdpudp.ack.vectorsize", FT_UINT16
, BASE_DEC
, NULL
, 0, NULL
, HFILL
}
808 { &hf_rdpudp_ack_item
,
809 {"Ack item", "rdpudp.ack.item", FT_UINT8
, BASE_HEX
, NULL
, 0, NULL
, HFILL
}
811 { &hf_rdpudp_ack_item_state
,
812 {"VECTOR_ELEMENT_STATE", "rdpudp.ack.item.state", FT_UINT8
, BASE_HEX
, VALS(rdpudp_ack_states_vals
), 0xc0, NULL
, HFILL
}
814 { &hf_rdpudp_ack_item_rle
,
815 {"Run length", "rdpudp.ack.item.rle", FT_UINT8
, BASE_DEC
, NULL
, 0x3f, NULL
, HFILL
}
817 { &hf_rdpudp_fec_coded
,
818 {"snCoded", "rdpudp.fec.coded", FT_UINT32
, BASE_HEX
, NULL
, 0, NULL
, HFILL
}
820 { &hf_rdpudp_fec_sourcestart
,
821 {"snSourceStart", "rdpudp.fec.sourcestart", FT_UINT32
, BASE_HEX
, NULL
, 0, NULL
, HFILL
}
823 { &hf_rdpudp_fec_range
,
824 {"Range", "rdpudp.fec.range", FT_UINT8
, BASE_DEC
, NULL
, 0, NULL
, HFILL
}
826 { &hf_rdpudp_fec_fecindex
,
827 {"Fec index", "rdpudp.fec.fecindex", FT_UINT8
, BASE_DEC
, NULL
, 0, NULL
, HFILL
}
829 { &hf_rdpudp_resetseqenum
,
830 {"snResetSeqNum", "rdpudp.resetSeqNum", FT_UINT32
, BASE_HEX
, NULL
, 0, NULL
, HFILL
}
832 { &hf_rdpudp_source_sncoded
,
833 {"snCoded", "rdpudp.data.sncoded", FT_UINT32
, BASE_HEX
, NULL
, 0, NULL
, HFILL
}
835 { &hf_rdpudp_source_snSourceStart
,
836 {"snSourceStart", "rdpudp.data.sourceStart", FT_UINT32
, BASE_HEX
, NULL
, 0, NULL
, HFILL
}
839 {"Data", "rdpudp.data", FT_BYTES
, BASE_NONE
, NULL
, 0, NULL
, HFILL
}
843 { &hf_rdpudp2_PacketPrefixByte
,
844 {"PacketPrefixByte", "rdpudp.prefixbyte", FT_UINT8
, BASE_HEX
, NULL
, 0, NULL
, HFILL
}
846 { &hf_rdpudp2_packetType
,
847 {"PacketType", "rdpudp.packetType", FT_UINT8
, BASE_HEX
, VALS(rdpudp2_packetType_vals
), 0x1e, NULL
, HFILL
}
849 { &hf_rdpudp2_shortPacketLength
,
850 {"Short packet length", "rdpudp.shortpacketlen", FT_UINT8
, BASE_DEC
, NULL
, 0x7, NULL
, HFILL
}
853 {"Flags", "rdpudp.flags", FT_UINT16
, BASE_HEX
, NULL
, 0x0fff, NULL
, HFILL
}
855 { &hf_rdpudp2_flag_ack
,
856 {"Ack", "rdpudp.flags.ack", FT_BOOLEAN
, 16, NULL
, RDPUDP2_ACK
, NULL
, HFILL
}
858 { &hf_rdpudp2_flag_data
,
859 {"Data", "rdpudp.flags.data", FT_BOOLEAN
, 16, NULL
, RDPUDP2_DATA
, NULL
, HFILL
}
861 { &hf_rdpudp2_flag_ackvec
,
862 {"AckVec", "rdpudp.flags.ackvec", FT_UINT16
, BASE_HEX
, NULL
, RDPUDP2_ACKVEC
, NULL
, HFILL
}
864 { &hf_rdpudp2_flag_aoa
,
865 {"AckOfAcks", "rdpudp.flags.ackofacks", FT_UINT16
, BASE_HEX
, NULL
, RDPUDP2_AOA
, NULL
, HFILL
}
867 { &hf_rdpudp2_flag_overhead
,
868 {"OverheadSize", "rdpudp.flags.overheadsize", FT_UINT16
, BASE_HEX
, NULL
, RDPUDP2_OVERHEAD
, NULL
, HFILL
}
870 { &hf_rdpudp2_flag_delayackinfo
,
871 {"DelayedAckInfo", "rdpudp.flags.delayackinfo", FT_UINT16
, BASE_HEX
, NULL
, RDPUDP2_DELAYACK
, NULL
, HFILL
}
873 { &hf_rdpudp2_logWindow
,
874 {"LogWindow", "rdpudp.logWindow", FT_UINT16
, BASE_DEC
, NULL
, 0xf000, NULL
, HFILL
}
876 { &hf_rdpudp2_AckSeq
,
877 {"Base Seq", "rdpudp.ack.seqnum", FT_UINT16
, BASE_HEX
, NULL
, 0, NULL
, HFILL
}
880 {"receivedTS", "rdpudp.ack.ts", FT_UINT24
, BASE_DEC
, NULL
, 0, NULL
, HFILL
}
882 { &hf_rdpudp2_AckSendTimeGap
,
883 {"sendTimeGap", "rdpudp.ack.sendTimeGap", FT_UINT8
, BASE_DEC
, NULL
, 0, NULL
, HFILL
}
885 { &hf_rdpudp2_ndelayedAcks
,
886 {"NumDelayedAcks", "rdpudp.ack.numDelayedAcks", FT_UINT8
, BASE_DEC
, NULL
, 0x0F, NULL
, HFILL
}
888 { &hf_rdpudp2_delayedTimeScale
,
889 {"delayedTimeScale", "rdpudp.ack.delayedTimeScale", FT_UINT8
, BASE_DEC
, NULL
, 0xF0, NULL
, HFILL
}
891 { &hf_rdpudp2_delayedAcks
,
892 {"Delayed acks", "rdpudp.ack.delayedAcks", FT_BYTES
, BASE_NONE
, NULL
, 0, NULL
, HFILL
}
894 { &hf_rdpudp2_delayedAck
,
895 {"Delayed ack", "rdpudp.ack.delayedAck", FT_UINT8
, BASE_DEC
, NULL
, 0, NULL
, HFILL
}
897 { &hf_rdpudp2_OverHeadSize
,
898 {"Overhead size", "rdpudp.overheadsize", FT_UINT8
, BASE_DEC
, NULL
, 0, NULL
, HFILL
}
900 { &hf_rdpudp2_DelayAckMax
,
901 {"MaxDelayedAcks", "rdpudp.delayackinfo.max", FT_UINT8
, BASE_DEC
, NULL
, 0, NULL
, HFILL
}
903 { &hf_rdpudp2_DelayAckTimeout
,
904 {"DelayedAckTimeoutInMs", "rdpudp.delayackinfo.timeout", FT_UINT16
, BASE_DEC
, NULL
, 0, NULL
, HFILL
}
906 { &hf_rdpudp2_AckOfAcksSeqNum
,
907 {"Sequence number", "rdpudp.ackofacksseqnum", FT_UINT16
, BASE_HEX
, NULL
, 0, NULL
, HFILL
}
909 { &hf_rdpudp2_DataSeqNumber
,
910 {"Sequence number", "rdpudp.data.seqnum", FT_UINT16
, BASE_HEX
, NULL
, 0, NULL
, HFILL
}
912 { &hf_rdpudp2_DataFullSeqNumber
,
913 {"Full sequence number", "rdpudp.data.fullseqnum", FT_UINT32
, BASE_HEX
, NULL
, 0, NULL
, HFILL
}
915 { &hf_rdpudp2_DataChannelSeqNumber
,
916 {"Channel sequence number", "rdpudp.data.channelseqnumber", FT_UINT16
, BASE_HEX
, NULL
, 0, NULL
, HFILL
}
918 { &hf_rdpudp2_DataChannelFullSeqNumber
,
919 {"Channel full sequence number", "rdpudp.data.channelfullseqnumber", FT_UINT32
, BASE_HEX
, NULL
, 0, NULL
, HFILL
}
922 {"Data", "rdpudp.data", FT_BYTES
, BASE_NONE
, NULL
, 0, NULL
, HFILL
}
924 { &hf_rdpudp2_AckvecBaseSeq
,
925 {"Base sequence number", "rdpudp.ackvec.baseseqnum", FT_UINT16
, BASE_HEX
, NULL
, 0, NULL
, HFILL
}
927 { &hf_rdpudp2_AckvecCodecAckVecSize
,
928 {"Coded ackvec size","rdpudp.ackvec.codedackvecsize", FT_UINT16
, BASE_DEC
, NULL
, 0x7f, NULL
, HFILL
}
930 { &hf_rdpudp2_AckvecHaveTs
,
931 {"Have timestamp", "rdpudp.ackvec.havets", FT_BOOLEAN
, 8, NULL
, 0x80, NULL
, HFILL
}
933 { &hf_rdpudp2_AckvecTimeStamp
,
934 {"Timestamp", "rdpudp.ackvec.timestamp", FT_UINT24
, BASE_HEX
, NULL
, 0, NULL
, HFILL
}
936 { &hf_rdpudp2_SendAckTimeGapInMs
,
937 {"SendAckTimeGap", "rdpudp.ackvec.sendacktimegap", FT_UINT8
, BASE_DEC
, NULL
, 0x00, NULL
, HFILL
}
939 { &hf_rdpudp2_AckvecCodedAck
,
940 {"Coded Ack", "rdpudp.ackvec.codedAck", FT_UINT8
, BASE_HEX
, NULL
, 0, NULL
, HFILL
}
942 { &hf_rdpudp2_AckvecCodedAckMode
,
943 {"Mode", "rdpudp.ackvec.codecAckMode", FT_UINT8
, BASE_HEX
, VALS(rdpudp2_ackvec_mode_vals
), 0x80, NULL
, HFILL
}
945 { &hf_rdpudp2_AckvecCodedAckRleState
,
946 {"State", "rdpudp.ackvec.codecAckRleState", FT_UINT8
, BASE_DEC
, VALS(rdpudp2_ackvec_rlestates_vals
), 0x40, NULL
, HFILL
}
948 { &hf_rdpudp2_AckvecCodedAckRleLen
,
949 {"Length", "rdpudp.ackvec.codecAckRleLen", FT_UINT8
, BASE_DEC
, NULL
, 0x3f, NULL
, HFILL
}
953 /* List of subtrees */
954 static int *ett
[] = {
961 &ett_rdpudp2_packetType
,
964 &ett_rdpudp2_overhead
,
965 &ett_rdpudp2_delayack
,
969 &ett_rdpudp2_ackvec_vecs
,
970 &ett_rdpudp2_ackvec_vec
,
973 /* Register protocol */
974 proto_rdpudp
= proto_register_protocol(PNAME
, PSNAME
, PFNAME
);
975 /* Register fields and subtrees */
976 proto_register_field_array(proto_rdpudp
, hf
, array_length(hf
));
977 proto_register_subtree_array(ett
, array_length(ett
));
979 rdpudp_handle
= register_dissector("rdpudp", dissect_rdpudp
, proto_rdpudp
);
983 proto_reg_handoff_rdpudp(void)
985 tls_handle
= find_dissector("tls");
986 dtls_handle
= find_dissector("dtls");
987 dissector_add_uint("udp.port", 3389, rdpudp_handle
);
991 * Editor modelines - https://www.wireshark.org/tools/modelines.html
996 * indent-tabs-mode: nil
999 * ex: set shiftwidth=2 tabstop=8 expandtab:
1000 * :indentSize=2:tabSize=8:noTabs=true: