aboutsummaryrefslogtreecommitdiffstats
path: root/epan/dissectors/packet-mtp2.c
blob: 389b79a09fd37d75fad6eb659baf18a02f8a0dad (plain)
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
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
/* packet-mtp2.c
 * Routines for MTP2 dissection
 * It is hopefully (needs testing) compliant to
 * ITU-T Q.703 and Q.703 Annex A.
 *
 * Copyright 2001, 2004 Michael Tuexen <tuexen [AT] fh-muenster.de>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * Copied from packet-m2pa.c
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "config.h"

#include <epan/packet.h>
#include <epan/prefs.h>
#include <epan/crc16-tvb.h>
#include <epan/expert.h>
#include <wiretap/wtap.h>
#include <epan/conversation.h>
#include <epan/proto_data.h>
#include <epan/reassemble.h>

void proto_register_mtp2(void);
void proto_reg_handoff_mtp2(void);

static dissector_handle_t mtp2_handle;
static dissector_handle_t mtp2_with_phdr_handle;
static dissector_handle_t mtp2_bitstream_handle;
static dissector_handle_t mtp3_handle;

/* possible packet states */
enum packet_direction_state_mtp2 {FORWARD, BACKWARD};

/* structure for telling the bitstream dissector if it shall use a mtp2_flag_search value from the prev. packet */
typedef struct mtp2_flag_search {
  gboolean      set;            /* shows if the mtp2_flag_search value is valid, needed to be set in the dissect function */
  guint8        mtp2_flag_search;    /* mtp2_flag_search value itself */
} mtp2_mtp2_flag_search_t;

/* Possible states of the state machine for decoding the MTP2 bitstream */
enum mtp2_bitstream_states {OUT_OF_SYNC, FLAGS, DATA};

/* data type for chained list of found MTP2 packets in RTP stream */
typedef struct mtp2_recognized_packet {
  tvbuff_t                *data;              /* data of the actual packet */
  guint8                  unalignment_offset; /* !=0 signals if this packet was not a multiple of 8 bits in the stream */
} mtp2_recognized_packet_t;

/* structure used in mtp2_dissect_tvb_res
   this contains the tvb's before the first and after last header */
typedef struct mtp2_remain_data {
  tvbuff_t        *before_first_flag;              /* data found before the first flag */
  tvbuff_t        *after_last_flag;                /* data found after the last flag */
  guint8          before_fh_unalignment_offset;    /* !=0 signals if the before_fh data was not a multiple of 8 bits in the stream */
  gboolean        before_fh_frame_reset;           /* signals if there was a frame reset in the data before the 1st flag */
} mtp2_remain_data_t;


/* structure to store the result of dissect_mtp2_tvb function */
typedef struct mtp2_dissect_tvb_res {
  mtp2_remain_data_t              mtp2_remain_data;       /* stores the tvbuffs found before 1st and after last flags in the packet */
  mtp2_mtp2_flag_search_t         mtp2_flag_search;       /* this contains the mtp2_flag_search's value at the end of the packet dissection */
  wmem_list_t                     *found_packets;         /* contains the packets found in tvbuff */
  guint8                          data_buff;              /* to store the data_buff value */
  guint8                          data_buff_offset;       /* to store the data_buff_offset value */
  guint8                          last_flag_beginning_offset_for_align_check;     /* the offset of the last flag's beginning have to be stored */
  gboolean                        flag_found;             /* boolean value to sign if there was a flag in the RTP packet or not */
  enum mtp2_bitstream_states      state;                  /* to store the value of the state of the dissection after finish */
} mtp2_dissect_tvb_res_t;

/* mtp2 per-packet data */
typedef struct mtp2_ppd {
  mtp2_mtp2_flag_search_t         mtp2_flag_search;                               /* flag search needed to pass to dissect_mtp2_tvb - it was derived from the prev. packet in the same direction */
  guint8                          data_buff;                                      /* data buff needed to pass to dissect_mtp2_tvb - it was derived from the prev. packet in the same direction */
  guint8                          data_buff_offset;                               /* data buff offset needed to pass to dissect_mtp2_tvb - it was derived from the prev. packet in the same direction */
  guint8                          last_flag_beginning_offset_for_align_check;     /* variable for align check, stores the last flag's beginning's offset */
  guint32                         reass_seq_num_for_reass_check_before_fh;        /* this is the id (reass_seq_num) which should be used for looking up reassembled data found before the first flag */
  guint32                         reass_seq_num_for_reass_check_after_lh;         /* this is the id (reass_seq_num) which should be used for looking up reassembled data found after the last flag */
  enum mtp2_bitstream_states      state;                                          /* state needed to pass to dissect_mtp2_tvb - it was derived from the prev. packet in the same direction */
} mtp2_ppd_t;

/* conversation data about the previous packet in the conversation (in one direction) */
typedef struct mtp2_convo_data_prev_packet {
  mtp2_mtp2_flag_search_t       mtp2_flag_search;       /* storing the prev. packet's flag search */
  guint8                        data_buff;              /* storing the prev. packet's data buffer */
  guint8                        data_buff_offset;       /* storing the prev. packet's data buffer offset */
  guint8                        last_flag_beginning_offset_for_align_check;     /* storing the prev. packet's last flag's offset */
  guint32                       reass_seq_num;          /* storing the prev. packet's reassemble seq. num */
  enum mtp2_bitstream_states    state;                  /* storing the prev. packet's state in the forward direction */
} mtp2_convo_data_prev_packet_t;

/* conversation data for MTP2 dissection from RTP payload */
typedef struct mtp2_convo_data {
  address         addr_a;                               /* storing the first packet's originating address */
  address         addr_b;                               /* storing the first packet's terminating address */
  guint32         port_a;                               /* storing the first packet's originating port */
  guint32         port_b;                               /* storing the first packet's terminating port */
  mtp2_convo_data_prev_packet_t   *forward;             /* storing needed info about the prev. packet's in forward direction */
  mtp2_convo_data_prev_packet_t   *backward;            /* storing needed info about the prev. packet's in backward direction */
} mtp2_convo_data_t;

/* Initialize the protocol and registered fields */
static int proto_mtp2;
static int hf_mtp2_bsn;
static int hf_mtp2_ext_bsn;
static int hf_mtp2_ext_res;
static int hf_mtp2_bib;
static int hf_mtp2_ext_bib;
static int hf_mtp2_fsn;
static int hf_mtp2_ext_fsn;
static int hf_mtp2_fib;
static int hf_mtp2_ext_fib;
static int hf_mtp2_li;
static int hf_mtp2_ext_li;
static int hf_mtp2_spare;
static int hf_mtp2_ext_spare;
static int hf_mtp2_sf;
static int hf_mtp2_sf_extra;
static int hf_mtp2_fcs_16;
static int hf_mtp2_fcs_16_status;
static int hf_mtp2_unexpect_end;
static int hf_mtp2_frame_reset;

/* reassemble variables */
static int hf_mtp2_fragments;
static int hf_mtp2_fragment;
static int hf_mtp2_fragment_overlap;
static int hf_mtp2_fragment_overlap_conflicts;
static int hf_mtp2_fragment_multiple_tails;
static int hf_mtp2_fragment_too_long_fragment;
static int hf_mtp2_fragment_error;
static int hf_mtp2_fragment_count;
static int hf_mtp2_reassembled_in;
static int hf_mtp2_reassembled_length;
static gint ett_mtp2_fragment;
static gint ett_mtp2_fragments;

/* local static const needed for reassembly */
static const fragment_items mtp2_frag_items = {
  &ett_mtp2_fragment,
  &ett_mtp2_fragments,
  &hf_mtp2_fragments,
  &hf_mtp2_fragment,
  &hf_mtp2_fragment_overlap,
  &hf_mtp2_fragment_overlap_conflicts,
  &hf_mtp2_fragment_multiple_tails,
  &hf_mtp2_fragment_too_long_fragment,
  &hf_mtp2_fragment_error,
  &hf_mtp2_fragment_count,
  &hf_mtp2_reassembled_in,
  &hf_mtp2_reassembled_length,
  NULL,
  "MTP2 Message fragments"
};

/* needed for packet reassembly */
static reassembly_table mtp2_reassembly_table;

/* variables needed for property registration to wireshark menu */
static gboolean reverse_bit_order_mtp2 = FALSE;

static expert_field ei_mtp2_checksum_error;
static expert_field ei_mtp2_li_bad;

/* Initialize the subtree pointers */
static gint ett_mtp2;

static gboolean use_extended_sequence_numbers_default = FALSE;
static gboolean capture_contains_fcs_crc_default = FALSE;

/* sequence number of the actual packet to be reassembled
 * this is needed because the reassemble handler uses a key based on the
 * source and destination IP addresses
 * therefore if there are multiple streams between 2 IP end-points
 * the reassemble sequence numbers can conflict if they are based on conversations */
static guint32    mtp2_absolute_reass_seq_num = 0;

#define BSN_BIB_LENGTH          1
#define FSN_FIB_LENGTH          1
#define LI_LENGTH               1
#define HEADER_LENGTH           (BSN_BIB_LENGTH + FSN_FIB_LENGTH + LI_LENGTH)

#define EXTENDED_BSN_BIB_LENGTH 2
#define EXTENDED_FSN_FIB_LENGTH 2
#define EXTENDED_LI_LENGTH      2
#define EXTENDED_HEADER_LENGTH  (EXTENDED_BSN_BIB_LENGTH + EXTENDED_FSN_FIB_LENGTH + EXTENDED_LI_LENGTH)

#define BSN_BIB_OFFSET          0
#define FSN_FIB_OFFSET          (BSN_BIB_OFFSET + BSN_BIB_LENGTH)
#define LI_OFFSET               (FSN_FIB_OFFSET + FSN_FIB_LENGTH)
#define SIO_OFFSET              (LI_OFFSET + LI_LENGTH)

#define EXTENDED_BSN_BIB_OFFSET 0
#define EXTENDED_FSN_FIB_OFFSET (EXTENDED_BSN_BIB_OFFSET + EXTENDED_BSN_BIB_LENGTH)
#define EXTENDED_LI_OFFSET      (EXTENDED_FSN_FIB_OFFSET + EXTENDED_FSN_FIB_LENGTH)
#define EXTENDED_SIO_OFFSET     (EXTENDED_LI_OFFSET + EXTENDED_LI_LENGTH)

#define BSN_MASK                0x7f
#define BIB_MASK                0x80
#define FSN_MASK                0x7f
#define FIB_MASK                0x80
#define LI_MASK                 0x3f
#define SPARE_MASK              0xc0

#define EXTENDED_BSN_MASK       0x0fff
#define EXTENDED_RES_MASK       0x7000
#define EXTENDED_BIB_MASK       0x8000
#define EXTENDED_FSN_MASK       0x0fff
#define EXTENDED_FIB_MASK       0x8000
#define EXTENDED_LI_MASK        0x01ff
#define EXTENDED_SPARE_MASK     0xfe00

/* remove comment to enable debugging of bitstream dissector
 * if enabled this produces printout to stderr like this for every packet:

start_dissect_bitstream_packet: 2120
11010010        SKIPPED ZEROS: 3.
10000000
01100001
01101111
10011111        SKIPPED ZEROS: 2.
10011001        FLAG FOUND
11110100        SKIPPED ZEROS: 5.
10100000
00011000
 * under the development it can be very helpful to see RTP packet content like this
 * to identify and solve problems regarding bitstream parsing*/
/*#define MTP2_BITSTREAM_DEBUG    1*/

#ifdef MTP2_BITSTREAM_DEBUG
#include <glib/gprintf.h>
#endif

static void
dissect_mtp2_header(tvbuff_t *su_tvb, packet_info *pinfo, proto_item *mtp2_tree, gboolean use_extended_sequence_numbers, gboolean validate_crc, guint32 *li)
{
  guint reported_len;
  proto_item *li_item;

  if (use_extended_sequence_numbers) {
    reported_len = tvb_reported_length_remaining(su_tvb, EXTENDED_HEADER_LENGTH);
    if (validate_crc) {
      reported_len = reported_len < 2 ? 0 : (reported_len - 2);
    }
    proto_tree_add_item(mtp2_tree, hf_mtp2_ext_bsn,   su_tvb, EXTENDED_BSN_BIB_OFFSET, EXTENDED_BSN_BIB_LENGTH, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(mtp2_tree, hf_mtp2_ext_res,   su_tvb, EXTENDED_BSN_BIB_OFFSET, EXTENDED_BSN_BIB_LENGTH, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(mtp2_tree, hf_mtp2_ext_bib,   su_tvb, EXTENDED_BSN_BIB_OFFSET, EXTENDED_BSN_BIB_LENGTH, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(mtp2_tree, hf_mtp2_ext_fsn,   su_tvb, EXTENDED_FSN_FIB_OFFSET, EXTENDED_FSN_FIB_LENGTH, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(mtp2_tree, hf_mtp2_ext_res,   su_tvb, EXTENDED_BSN_BIB_OFFSET, EXTENDED_BSN_BIB_LENGTH, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(mtp2_tree, hf_mtp2_ext_fib,   su_tvb, EXTENDED_FSN_FIB_OFFSET, EXTENDED_FSN_FIB_LENGTH, ENC_LITTLE_ENDIAN);
    li_item = proto_tree_add_item_ret_uint(mtp2_tree, hf_mtp2_ext_li, su_tvb, EXTENDED_LI_OFFSET, EXTENDED_LI_LENGTH, ENC_LITTLE_ENDIAN, li);
    if (*li != reported_len) {
      /* ITU-T Q.703 A.2.3.3 When the extended sequence numbers are used,
       * the field is large enough to contain all legal values. 0-273.
       * Thus the check in the other case doesn't apply. */
      proto_item_append_text(li_item, " [expected payload length %u]", reported_len);
      expert_add_info_format(pinfo, li_item, &ei_mtp2_li_bad, "Bad length value %u != payload length ", *li);
      col_append_fstr(pinfo->cinfo, COL_INFO, " [BAD MTP2 LI %u != PAYLOAD LENGTH]", *li);
    }
    proto_tree_add_item(mtp2_tree, hf_mtp2_ext_spare, su_tvb, EXTENDED_LI_OFFSET,      EXTENDED_LI_LENGTH,      ENC_LITTLE_ENDIAN);
  } else {
    reported_len = tvb_reported_length_remaining(su_tvb, HEADER_LENGTH);
    if (validate_crc) {
      reported_len = reported_len < 2 ? 0 : (reported_len - 2);
    }
    proto_tree_add_item(mtp2_tree, hf_mtp2_bsn,   su_tvb, BSN_BIB_OFFSET, BSN_BIB_LENGTH, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(mtp2_tree, hf_mtp2_bib,   su_tvb, BSN_BIB_OFFSET, BSN_BIB_LENGTH, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(mtp2_tree, hf_mtp2_fsn,   su_tvb, FSN_FIB_OFFSET, FSN_FIB_LENGTH, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(mtp2_tree, hf_mtp2_fib,   su_tvb, FSN_FIB_OFFSET, FSN_FIB_LENGTH, ENC_LITTLE_ENDIAN);
    li_item = proto_tree_add_item_ret_uint(mtp2_tree, hf_mtp2_li, su_tvb, LI_OFFSET, LI_LENGTH, ENC_LITTLE_ENDIAN, li);
    /* ITU-T Q.703 2.3.3: In the case that the payload is more than the
     * li field allows, should be set to the max, i.e. the mask 63 */
    if (reported_len > LI_MASK) {
      if (*li != LI_MASK) {
        proto_item_append_text(li_item, " [payload length %u, expected max value %u]", reported_len, LI_MASK);
        expert_add_info_format(pinfo, li_item, &ei_mtp2_li_bad, "Bad length value %u != max value ", *li);
        col_append_fstr(pinfo->cinfo, COL_INFO, " [BAD MTP2 LI %u != MAX VALUE]", *li);
      }
    } else if (*li != reported_len ) {
      proto_item_append_text(li_item, " [expected payload length %u]", reported_len);
      expert_add_info_format(pinfo, li_item, &ei_mtp2_li_bad, "Bad length value %u != payload length ", *li);
      col_append_fstr(pinfo->cinfo, COL_INFO, " [BAD MTP2 LI %u != PAYLOAD LENGTH]", *li);
    }
    proto_tree_add_item(mtp2_tree, hf_mtp2_spare, su_tvb, LI_OFFSET,      LI_LENGTH,      ENC_LITTLE_ENDIAN);
  }
}
/*
*******************************************************************************
* DETAILS : Calculate a new FCS-16 given the current FCS-16 and the new data.
*******************************************************************************
*/
static guint16
mtp2_fcs16(tvbuff_t * tvbuff)
{
  guint len = tvb_reported_length(tvbuff)-2;

  /* Check for Invalid Length */
  if (len == 0)
    return (0x0000);
  return crc16_ccitt_tvb(tvbuff, len);
}

/*
 * This function for CRC16 only is based on the decode_fcs of packet_ppp.c
 */
static tvbuff_t *
mtp2_decode_crc16(tvbuff_t *tvb, proto_tree *fh_tree, packet_info *pinfo)
{
  tvbuff_t   *next_tvb;
  gint       len, reported_len;
  int proto_offset=0;

  /*
   * Do we have the entire packet, and does it include a 2-byte FCS?
   */
  len = tvb_reported_length_remaining(tvb, proto_offset);
  reported_len = tvb_reported_length_remaining(tvb, proto_offset);
  if (reported_len < 2 || len < 0) {
    /*
     * The packet is claimed not to even have enough data for a 2-byte FCS,
     * or we're already past the end of the captured data.
     * Don't slice anything off.
     */
    next_tvb = tvb_new_subset_remaining(tvb, proto_offset);
  } else if (len < reported_len) {
    /*
     * The packet is claimed to have enough data for a 2-byte FCS, but
     * we didn't capture all of the packet.
     * Slice off the 2-byte FCS from the reported length, and trim the
     * captured length so it's no more than the reported length; that
     * will slice off what of the FCS, if any, is in the captured
     * length.
     */
    reported_len -= 2;
    if (len > reported_len)
      len = reported_len;
    next_tvb = tvb_new_subset_length_caplen(tvb, proto_offset, len, reported_len);
  } else {
    /*
     * We have the entire packet, and it includes a 2-byte FCS.
     * Slice it off.
     */
    len -= 2;
    reported_len -= 2;
    next_tvb = tvb_new_subset_length_caplen(tvb, proto_offset, len, reported_len);

    /*
     * Compute the FCS and put it into the tree.
     */
    proto_tree_add_checksum(fh_tree, tvb, proto_offset + len, hf_mtp2_fcs_16, hf_mtp2_fcs_16_status, &ei_mtp2_checksum_error, pinfo, mtp2_fcs16(tvb),
                            ENC_LITTLE_ENDIAN, PROTO_CHECKSUM_VERIFY);
  }
  return next_tvb;
}


static void
dissect_mtp2_fisu(packet_info *pinfo)
{
  col_set_str(pinfo->cinfo, COL_INFO, "FISU ");
}

static const value_string status_field_vals[] = {
  { 0x0, "Status Indication O" },
  { 0x1, "Status Indication N" },
  { 0x2, "Status Indication E" },
  { 0x3, "Status Indication OS" },
  { 0x4, "Status Indication PO" },
  { 0x5, "Status Indication B" },
  { 0,   NULL}
};

/* Same as above but in acronym form (for the Info column) */
static const value_string status_field_acro_vals[] = {
  { 0x0, "SIO" },
  { 0x1, "SIN" },
  { 0x2, "SIE" },
  { 0x3, "SIOS" },
  { 0x4, "SIPO" },
  { 0x5, "SIB" },
  { 0,   NULL}
};

#define SF_OFFSET          (LI_OFFSET + LI_LENGTH)
#define EXTENDED_SF_OFFSET (EXTENDED_LI_OFFSET + EXTENDED_LI_LENGTH)

#define SF_LENGTH                       1
#define SF_EXTRA_OFFSET                 (SF_OFFSET + SF_LENGTH)
#define EXTENDED_SF_EXTRA_OFFSET        (EXTENDED_SF_OFFSET + SF_LENGTH)
#define SF_EXTRA_LENGTH                 1

static void
dissect_mtp2_lssu(tvbuff_t *su_tvb, packet_info *pinfo, proto_item *mtp2_tree,
                  gboolean use_extended_sequence_numbers)
{
  guint8 sf = 0xFF;
  guint8 sf_offset, sf_extra_offset;

  if (use_extended_sequence_numbers) {
    sf_offset = EXTENDED_SF_OFFSET;
    sf_extra_offset = EXTENDED_SF_EXTRA_OFFSET;
  } else {
    sf_offset = SF_OFFSET;
    sf_extra_offset = SF_EXTRA_OFFSET;
  }

  proto_tree_add_item(mtp2_tree, hf_mtp2_sf, su_tvb, sf_offset, SF_LENGTH, ENC_LITTLE_ENDIAN);
  sf = tvb_get_guint8(su_tvb, SF_OFFSET);

  /*  If the LI is 2 then there is an extra octet following the standard SF
   *  field but it is not defined what this octet is.
   *  (In any case the first byte of the SF always has the same meaning.)
   */
  if ((tvb_get_guint8(su_tvb, LI_OFFSET) & LI_MASK) == 2)
    proto_tree_add_item(mtp2_tree, hf_mtp2_sf_extra, su_tvb, sf_extra_offset, SF_EXTRA_LENGTH, ENC_LITTLE_ENDIAN);

  col_set_str(pinfo->cinfo, COL_INFO, val_to_str_const(sf, status_field_acro_vals, "Unknown"));
}

static void
dissect_mtp2_msu(tvbuff_t *su_tvb, packet_info *pinfo, proto_item *mtp2_item,
                 proto_item *tree, gboolean use_extended_sequence_numbers)
{
  gint sif_sio_length;
  tvbuff_t *sif_sio_tvb;

  col_set_str(pinfo->cinfo, COL_INFO, "MSU ");

  if (use_extended_sequence_numbers) {
    sif_sio_length = tvb_reported_length(su_tvb) - EXTENDED_HEADER_LENGTH;
    sif_sio_tvb = tvb_new_subset_length(su_tvb, EXTENDED_SIO_OFFSET, sif_sio_length);
  } else {
    sif_sio_length = tvb_reported_length(su_tvb) - HEADER_LENGTH;
    sif_sio_tvb = tvb_new_subset_length(su_tvb, SIO_OFFSET, sif_sio_length);
  }
  call_dissector(mtp3_handle, sif_sio_tvb, pinfo, tree);

  if (tree) {
    if (use_extended_sequence_numbers)
      proto_item_set_len(mtp2_item, EXTENDED_HEADER_LENGTH);
    else
      proto_item_set_len(mtp2_item, HEADER_LENGTH);
  }
}

static void
dissect_mtp2_su(tvbuff_t *su_tvb, packet_info *pinfo, proto_item *mtp2_item,
                proto_item *mtp2_tree, proto_tree *tree, gboolean validate_crc,
                gboolean use_extended_sequence_numbers)
{
  guint32 li=0;
  tvbuff_t  *next_tvb = NULL;

  dissect_mtp2_header(su_tvb, pinfo, mtp2_tree, use_extended_sequence_numbers, validate_crc, &li);
  /* In some capture files (like .rf5), CRC are not present */
  /* So, to avoid trouble, give the complete buffer if CRC validation is disabled */
  if (validate_crc)
    next_tvb = mtp2_decode_crc16(su_tvb, mtp2_tree, pinfo);
  else
    next_tvb = su_tvb;

  switch(li) {
  case 0:
    dissect_mtp2_fisu(pinfo);
    break;
  case 1:
  case 2:
    dissect_mtp2_lssu(next_tvb, pinfo, mtp2_tree, use_extended_sequence_numbers);
    break;
  default:
    dissect_mtp2_msu(next_tvb, pinfo, mtp2_item, tree, use_extended_sequence_numbers);
    break;
  }
}

static void
dissect_mtp2_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
                    gboolean validate_crc, gboolean use_extended_sequence_numbers)
{
  proto_item *mtp2_item;
  proto_tree *mtp2_tree;

  col_set_str(pinfo->cinfo, COL_PROTOCOL, "MTP2");

  mtp2_item = proto_tree_add_item(tree, proto_mtp2, tvb, 0, -1, ENC_NA);
  mtp2_tree = proto_item_add_subtree(mtp2_item, ett_mtp2);

  dissect_mtp2_su(tvb, pinfo, mtp2_item, mtp2_tree, tree, validate_crc,
                  use_extended_sequence_numbers);
}

/* Dissect MTP2 frame without CRC16 and with a pseudo-header */
static int
dissect_mtp2_with_phdr(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
{
  if (pinfo->pseudo_header->mtp2.annex_a_used == MTP2_ANNEX_A_USED_UNKNOWN)
    dissect_mtp2_common(tvb, pinfo, tree, FALSE, use_extended_sequence_numbers_default);
  else
    dissect_mtp2_common(tvb, pinfo, tree, FALSE,
                        (pinfo->pseudo_header->mtp2.annex_a_used == MTP2_ANNEX_A_USED));

  return tvb_captured_length(tvb);
}

/* Dissect MTP2 frame with CRC16 included at end of payload. Used
 * if the user has associated "mtp2_with_crc" with a DLT or if the
 * packets come from an Endace ERF file.
 */
static int
dissect_mtp2_with_crc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
{
  dissect_mtp2_common(tvb, pinfo, tree, TRUE, use_extended_sequence_numbers_default);
  return tvb_captured_length(tvb);
}

/* Dissect MTP2 frame where we don't know if the CRC16 is included at
 * end of payload or not.
 */
static int
dissect_mtp2(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
{
  dissect_mtp2_common(tvb, pinfo, tree, capture_contains_fcs_crc_default,
                      use_extended_sequence_numbers_default);
  return tvb_captured_length(tvb);
}

static void
mtp2_init_routine(void)
{
        reassembly_table_register(&mtp2_reassembly_table, &addresses_ports_reassembly_table_functions);
}

/*  get one bit of a guint8 byte
*   based on the order set in the preferences
*   reverse_bit_order_mtp2 = FALSE: as the Q.703 states
*   reverse_bit_order_mtp2 = TRUE: just the opposite
*/
static gboolean
get_bit(guint8 byte, guint8 bit)
{
  if (reverse_bit_order_mtp2 == FALSE) {
    return byte & ((0x80 >> (bit-1))) ? TRUE : FALSE;
  } else {
    return byte & ((0x01 << (bit-1))) ? TRUE : FALSE;
  }
}

/* store new byte of an MTP2 frame in an array
 * after the whole packet is stored the array will be used to construct a new tvb */
static void
new_byte(char full_byte, guint8 **data, guint8 *data_len)
{
  guint8  *new_data = NULL;
  int     i = 0;

  if ((*data_len) == 0) {
    /* if data was never stored in this buffer before */
    *data = wmem_new(wmem_packet_scope(), guint8);
    (**data) = full_byte;
    (*data_len)++;
  } else {
    /* if this buffer is used -> create a completely new one
     * note, that after the dissection of this packet
     * the old data will be freed automatically (because of the wmem_alloc) */
    new_data = (guint8 *)wmem_alloc(wmem_packet_scope(), sizeof(guint8)*((*data_len)+1));
    /* copy the old one's content */
    for (i = 0;i<(*data_len);i++) {
      *(new_data+i) = *((*data)+i);
    }
    /* store the new data */
    *(new_data+*data_len) = full_byte;
    /* re-point the pointer to the new structure's head */
    *data = new_data;
    (*data_len)++;
  }
}

#ifdef MTP2_BITSTREAM_DEBUG
/* print debug info to stderr if debug is enabled
 * this function prints the packet bytes as bits separated by new lines
 * and adds extra info to bytes (flag found, frame reset, zeros were skipped, etc. */
static void debug(char *format, ...) G_GNUC_PRINTF(1, 2);
static void debug(char *format, ...)
{
  guint32 max_buffer_length = 256;
  gchar *buffer = NULL;
  va_list args;

  buffer = (gchar *) wmem_alloc(wmem_packet_scope(), max_buffer_length);
  buffer[0] = '\0';

  va_start(args,format);
  vsnprintf(buffer,max_buffer_length,format,args);
  g_printf("%s",buffer);
  va_end (args);
}
#endif

/* based on the actual packet's addresses and ports,
 * this function determines the packet's direction */
static enum packet_direction_state_mtp2
get_direction_state(packet_info *pinfo, mtp2_convo_data_t *convo_data)
{
  if (convo_data != NULL) {
    if (addresses_equal(&convo_data->addr_a, &pinfo->src)
        && addresses_equal(&convo_data->addr_b, &pinfo->dst)
        && convo_data->port_a == pinfo->srcport
        && convo_data->port_b == pinfo->destport) {
      return FORWARD;
    } else if (addresses_equal(&convo_data->addr_b, &pinfo->src)
        && addresses_equal(&convo_data->addr_a, &pinfo->dst)
        && convo_data->port_b == pinfo->srcport
        && convo_data->port_a == pinfo->destport) {
      return BACKWARD;
    }
  }

  return FORWARD;
}

/* prepares the data to be stored as found packet in wmem_list */
static mtp2_recognized_packet_t*
prepare_data_for_found_packet(tvbuff_t *tvb, guint8 unalignment_offset)
{
  mtp2_recognized_packet_t *packet;

  packet = wmem_new(wmem_packet_scope(), mtp2_recognized_packet_t);
  /* store values */
  packet->data = tvb;
  packet->unalignment_offset = unalignment_offset;

  return packet;
}

/* this function does the actual dissection of a tvb got from the RTP dissector
 * sets the mtp2_flag_search, data_buffer, it's offset and the state to the one which was stored
 * at the end of the previous packet's dissection in the same direction */
static mtp2_dissect_tvb_res_t*
dissect_mtp2_tvb(tvbuff_t* tvb, mtp2_mtp2_flag_search_t back_mtp2_flag_search, guint8 back_data_buff, guint8 back_data_buff_offset,
    enum mtp2_bitstream_states back_state, guint8 back_last_flag_beginning_offset_for_align_check)
{
  guint8        mtp2_flag_search = 0x00,                        /* this helps to detect the flags in the bitstream */
                data_buff = 0x00,                               /* buffer to store the found bits without the stuffed zeros */
                data_buff_offset = 0,                           /* index of the data_buff_offset, where to store the next bit */
                available_bytes_in_rtp_payload = 0,             /* stores the tvb's length which need to be analized */
                *found_data_buff_byte = NULL,                   /* buffer to store the found data_buff bytes till they are assembled to a tvb */
                offset = 0,                                     /* offset of the tvb, needed to get the appropriate byte */
                data_len = 0,                                   /* the length of the array where the data_buff's are stored */
                flag_beginning_offset_for_align_check = 0;      /* this stores the offset of the fist bit in a flag */
#ifdef MTP2_BITSTREAM_DEBUG
  gboolean      zero_skip0 = 0,                                 /* needed for debug output */
                zero_skip1 = 0,                                 /* needed for debug output */
                flag = FALSE,                                   /* needed for debug to print flag found message. reseted at every new octet read from tvb */
                frame_reset = FALSE;                            /* needed for debug, informs about a frame reset */
#endif
  enum mtp2_bitstream_states    state = OUT_OF_SYNC;            /* actual state of the dissection */
  tvbuff_t                      *new_tvb = NULL;                /* tvbuff which stores the assembled data from the data pointer */
  mtp2_dissect_tvb_res_t        *result = NULL;                 /* the result structure */

  /* initialize the result structure, this will be returned at the end */
  result = wmem_new(wmem_packet_scope(), mtp2_dissect_tvb_res_t);
  result->mtp2_remain_data.before_first_flag = NULL;
  result->mtp2_remain_data.before_fh_unalignment_offset = 0;
  result->mtp2_remain_data.before_fh_frame_reset = FALSE;
  result->mtp2_remain_data.after_last_flag = NULL;
  result->found_packets = wmem_list_new(wmem_packet_scope());
  result->flag_found = FALSE;
  result->last_flag_beginning_offset_for_align_check = 0;

  /* set the mtp2_flag_search if it is set */
  if (back_mtp2_flag_search.set == TRUE) {
    mtp2_flag_search = back_mtp2_flag_search.mtp2_flag_search;
  }
  /* set every other variables from the prev. packet's end in the same direction */
  data_buff = back_data_buff;
  data_buff_offset = back_data_buff_offset;
  state = back_state;
  flag_beginning_offset_for_align_check = back_last_flag_beginning_offset_for_align_check;

  /* determine how many byte are in the RTP payload */
  available_bytes_in_rtp_payload = tvb_reported_length_remaining(tvb, offset);

  /* walk through the tvb in means of octets */
  while (offset < available_bytes_in_rtp_payload) {
    /* get actual packet's byte */
    guint8 byte = tvb_get_guint8(tvb,offset);
    /* for every bit in the byte */
    for (guint8 i=1; i <= 8; i++) {
      /* get the bit's boolean value got from byte[i] */
      gboolean bit = get_bit(byte, i);

#ifdef MTP2_BITSTREAM_DEBUG
      /* in case of debug, print just the pure RTP payload, not the previous packet's end */
      debug("%u",(bit==FALSE?0:1));
#endif

      /* update the mtp2_flag_search */
      mtp2_flag_search = (mtp2_flag_search << 1) | bit;

      /* this section contains actions to be taken when the state is not OUT_OF_SYNC like
       * skipping zeros after every 5 bits */
      if (state != OUT_OF_SYNC) {
        /* The only values of mtp2_flag_search if we need to drop a zero is 0xBE and 0x3E */
        if ( (mtp2_flag_search == 0xBE || mtp2_flag_search == 0x3E)) {
#ifdef MTP2_BITSTREAM_DEBUG
          /* set the debug variables */
          if (zero_skip0 == 0)
            zero_skip0 = i;
          else
            zero_skip1 = i;
#endif
          /* if we need to skip a zero, the next flag offset have to be incremented because the raw data
           * between 2 flags is not a multiple of 8 bits now */
          flag_beginning_offset_for_align_check = (flag_beginning_offset_for_align_check + 1) % 8;
        } else {
          /* No drop -> store the value */
          data_buff = data_buff | (bit << data_buff_offset);
          data_buff_offset++;
          /* when a new complete byte without zeros was found */
          if (data_buff_offset == 8) {
            /* we don't store flags */
            if (data_buff != 0x7E) {
              /* store the data and change the state */
              state = DATA;
              new_byte(data_buff, &found_data_buff_byte, &data_len);
            }
            /* clear data_buff and it's offset */
            data_buff = 0x00;
            data_buff_offset = 0;
          }
        }
      }
      /* we have a flag */
      if (mtp2_flag_search == 0x7E &&
          !(offset == 0 && i < 8 && back_mtp2_flag_search.set == FALSE))
        /* the second part of the '&&' is not to recognize the 1111110x pattern as flag in the beginning of the 1st packet in every direction
         * the 1111110 would be shifted into the mtp2_flag_search variable, this has a 0x00 initial value
         * so after shifting 7 bits, the value of mtp2_flag_search would be 01111110 however there was no leading 0*/
      {
        /* set the state */
        state = FLAGS;
        /* if before this flag, we found some real packet related btyes */
        if (data_len != 0) {
          guint8 unaligned_packet_offset = 0; /* !=0 signals if the packet just found was not a multiple of 8 bits in the bitstream */
          /* here we check if the just found MTP2 packet is unaligned or not
           * 0 is not valid value meaning the flag_beginning_offset_for_align_check was not set at the beginning of the func.
           * if flag_beginning_offset_for_align_check != i, we have an unaligned packet */
          if (flag_beginning_offset_for_align_check != 0 && flag_beginning_offset_for_align_check != i) {
            /* set the unaligned offset */
            unaligned_packet_offset = i;
            /* clear the data_buff and offset
             * this is needed because at the next flag we would have data_buff offset unaligned
             * and we would find a 1 length packet with a part of the flag in it */
            data_buff = 0x00;
            data_buff_offset = 0;
          }
          /* fill the temporary buffer with data */
          guint8 *buff = (guint8 *) wmem_memdup(wmem_packet_scope(), found_data_buff_byte, data_len);
          /* Allocate new tvb for the proto frame */
          new_tvb = tvb_new_child_real_data(tvb, buff, data_len, data_len);
          /* if there were no flags before, we've found the bytes before the first flag */
          if (result->flag_found == FALSE) {
            /* this tvb is the one we found before the 1st flag */
            result->mtp2_remain_data.before_first_flag = new_tvb;
            /* if the bytes before the first flag was unaligned -> the calling function needs this info */
            result->mtp2_remain_data.before_fh_unalignment_offset = unaligned_packet_offset;
          } else {
            /* add the packet to the processable packet's list */
            wmem_list_append(result->found_packets, prepare_data_for_found_packet(new_tvb,unaligned_packet_offset));
          }
          /* clear data array (free will be done automatically) */
          data_len = 0;
          found_data_buff_byte = NULL;
        }

        flag_beginning_offset_for_align_check = i;
#ifdef MTP2_BITSTREAM_DEBUG
        /* for local debug purposes */
        flag = TRUE;
#endif
        /* set the result found in the result to TRUE */
        result->flag_found = TRUE;
        /* 7 consecutive 1s => out of sync */
      } else if (mtp2_flag_search == 0x7F || mtp2_flag_search == 0xFE || mtp2_flag_search == 0xFF) {
        /* set the state and clear everything */
        state = OUT_OF_SYNC;
        data_len = 0;
        found_data_buff_byte = NULL;
        data_buff = 0x00;
        data_buff_offset = 0;
#ifdef MTP2_BITSTREAM_DEBUG
        frame_reset = TRUE;
#endif
        if (result->flag_found == FALSE)
          result->mtp2_remain_data.before_fh_frame_reset = TRUE;
      }
    }

#ifdef MTP2_BITSTREAM_DEBUG
    /* if there were flag, print debug info */
    if (flag) {
      debug("\tFLAG FOUND");
    }
    /* if there were zeros skipped, print the debug data */
    if (!zero_skip0 == 0) {
      debug("\tSKIPPED ZEROS: %u.",zero_skip0);
      if (!zero_skip1 == 0)
        debug(" %u",zero_skip1);
    }
    /* if there was frame reset, print debug info */
    if (frame_reset) {
      debug("\tFRAME RESET");
    }
    /* print a \n to print the next byte in a different row */
    debug("\n");
    /* after every byte read from tvb clear debug stuff */
    zero_skip0 = 0;
    zero_skip1 = 0;
    /* set the debug variables as well */
    flag = FALSE;
    frame_reset = FALSE;
#endif
    /* increment tvb offset */
    offset++;
  }

  if (data_len != 0) {
    /* fill the temporary buffer with data */
    guint8 * buff = (guint8 *) wmem_memdup(wmem_packet_scope(), found_data_buff_byte, data_len);
    /* Allocate new tvb for the MTP2 frame */
    new_tvb = tvb_new_child_real_data(tvb, buff, data_len, data_len);
    /* this tvb is the one we found after the last flag */
    result->mtp2_remain_data.after_last_flag = new_tvb;
  }

  /* we do not return NULL in before_first_flag because then the reassemble will not work
   * we have to add a 0 length tvb with the flag "no more packets" */
  if (result->mtp2_remain_data.before_first_flag == NULL) {
    /* fill the temporary buffer with data */
    guint8 *buff = (guint8 *) wmem_memdup(wmem_packet_scope(), found_data_buff_byte, 0);
    /* Allocate new tvb for the MTP2 frame */
    new_tvb = tvb_new_child_real_data(tvb, buff, 0, 0);
    /* this tvb is the one we found after the last flag */
    result->mtp2_remain_data.before_first_flag = new_tvb;
  }

  /* don't set mtp2_flag_search and other stuff if the packet ended in out_of_sync */
  if (state != OUT_OF_SYNC) {
    result->mtp2_flag_search.set = TRUE;
    result->mtp2_flag_search.mtp2_flag_search = mtp2_flag_search;
    result->data_buff = data_buff;
    result->data_buff_offset = data_buff_offset;
  } else {
    result->mtp2_flag_search.set = FALSE;
    result->mtp2_flag_search.mtp2_flag_search = result->data_buff = result->data_buff_offset = 0x00;
  }

  /* set the state as well, in every case */
  result->state = state;

  /* set the last_flag_beginning_offset_for_align_check in the result structure */
  result->last_flag_beginning_offset_for_align_check = flag_beginning_offset_for_align_check;

  /* return the result structure */
  return result;
}

/* function to get a new reass. sequence number  */
static guint32
get_new_reass_seq_num(void)
{
  /* fail if it reached the max value */
  DISSECTOR_ASSERT(mtp2_absolute_reass_seq_num < 0xFFFFFFFE);
  mtp2_absolute_reass_seq_num++;

  return mtp2_absolute_reass_seq_num;
}


/* sign if the packet is unaligned in proto tree */
static void
issue_unaligned_info(proto_tree *tree, tvbuff_t *tvb, guint8 unalignment_offset)
{
  proto_tree_add_none_format(tree, hf_mtp2_unexpect_end, tvb, 0, tvb_reported_length_remaining(tvb,0),
      "[Packet ended in the middle of an octet. Octet: last, Offset: %u]",
      unalignment_offset);
}

/* sign if the packet is unaligned in proto tree */
static void
issue_frame_reset_info(proto_tree *tree, tvbuff_t *tvb)
{
  proto_tree_add_none_format(tree, hf_mtp2_frame_reset, tvb, 0, 0,
              "[Frame Reset Occurred, No Reassembly]");
}

/* set per packet data based on direction data */
static void
set_ppd_fields_based_on_convo_directon_data(mtp2_ppd_t *mtp2_ppd, mtp2_convo_data_prev_packet_t *direction_data)
{
  mtp2_ppd->mtp2_flag_search = direction_data->mtp2_flag_search;
  mtp2_ppd->data_buff = direction_data->data_buff;
  mtp2_ppd->data_buff_offset = direction_data->data_buff_offset;
  mtp2_ppd->state = direction_data->state;
  /* this is because the segment which will be reassembled in this packet
   * is stored with the reass_seq_num stored in the convo data
   * therefore we have to save this value for dissection in the future */
  mtp2_ppd->reass_seq_num_for_reass_check_before_fh = direction_data->reass_seq_num;
  mtp2_ppd->last_flag_beginning_offset_for_align_check = direction_data->last_flag_beginning_offset_for_align_check;
}

/* set convo data based on dissection result and reass_seq_num */
static void
set_direction_fields_based_on_result_and_reass_seq_num(mtp2_convo_data_prev_packet_t *direction_data, mtp2_dissect_tvb_res_t *result, guint32 reass_seq_num)
{
  direction_data->mtp2_flag_search = result->mtp2_flag_search;
  direction_data->data_buff = result->data_buff;
  direction_data->data_buff_offset = result->data_buff_offset;
  direction_data->state = result->state;
  direction_data->reass_seq_num = reass_seq_num;
  direction_data->last_flag_beginning_offset_for_align_check = result->last_flag_beginning_offset_for_align_check;
}

/* function to dissect bitstream data of MTP2 */
static int
dissect_mtp2_bitstream(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void * user_data _U_)
{
  guint32                               reass_seq_num = 0;                      /* reassemble sequence number at the beginning of this packet's dissection */
  conversation_t                        *conversation = NULL;                   /* conversation of the mtp2 dissection */
  mtp2_convo_data_t                     *convo_data = NULL;                     /* conversation data of the mtp2 dissection */
  mtp2_dissect_tvb_res_t                *result = NULL;                         /* variable to store the result of dissect_mtp2_tvb */
  enum packet_direction_state_mtp2      dir_state = FORWARD;                    /* direction state of this packet in the conversation */
  mtp2_ppd_t                            *mtp2_ppd = NULL;                       /* per-packet data of this packet */

#ifdef MTP2_BITSTREAM_DEBUG
  debug("start_dissect_bitstream_packet: %u\n",pinfo->fd->num);
#endif

  /* find conversation related to this packet */
  conversation = find_conversation(pinfo->fd->num,&pinfo->src, &pinfo->dst,conversation_pt_to_conversation_type(pinfo->ptype), pinfo->srcport, pinfo->destport, 0);
  /* if there is no conversation or it does not contain the per packet data we need */
  if (conversation == NULL) {
    /* there was no conversation => this packet is the first in a new conversation => let's create it */
    /* here we decide about the direction, every following packet with the same direction as this first one will be a forward packet */
    conversation = conversation_new(pinfo->fd->num,&pinfo->src, &pinfo->dst,conversation_pt_to_conversation_type(pinfo->ptype), pinfo->srcport, pinfo->destport, 0);
  }

  /* there is no proto data in the conversation */
  if (conversation_get_proto_data(conversation, proto_mtp2) == NULL) {
    /* create a new convo data and fill it with initial data */
    convo_data = wmem_new(wmem_file_scope(), mtp2_convo_data_t);
    copy_address_wmem(wmem_file_scope(), &convo_data->addr_a, &pinfo->src);
    copy_address_wmem(wmem_file_scope(), &convo_data->addr_b, &pinfo->dst);
    convo_data->port_a = pinfo->srcport;
    convo_data->port_b = pinfo->destport;
    convo_data->forward = wmem_new(wmem_file_scope(), mtp2_convo_data_prev_packet_t);
    convo_data->backward = wmem_new(wmem_file_scope(), mtp2_convo_data_prev_packet_t);
    convo_data->forward->mtp2_flag_search.set = convo_data->backward->mtp2_flag_search.set= FALSE;
    convo_data->forward->mtp2_flag_search.mtp2_flag_search = convo_data->backward->mtp2_flag_search.mtp2_flag_search = 0x00;
    convo_data->forward->data_buff = convo_data->backward->data_buff = 0x00;
    convo_data->forward->data_buff_offset = convo_data->backward->data_buff_offset = 0;
    convo_data->forward->state = convo_data->backward->state = OUT_OF_SYNC;
    convo_data->forward->reass_seq_num = get_new_reass_seq_num();
    convo_data->backward->reass_seq_num = get_new_reass_seq_num();
    convo_data->forward->last_flag_beginning_offset_for_align_check = convo_data->backward->last_flag_beginning_offset_for_align_check = 0;
    /* store the convo data */
    conversation_add_proto_data(conversation, proto_mtp2, convo_data);
  } else {
    /* the packet is part of an existing conversation => get the conversation data */
    convo_data = (mtp2_convo_data_t*)conversation_get_proto_data(conversation, proto_mtp2);
  }

  /* get the packet's state */
  dir_state = get_direction_state(pinfo, convo_data);

  /* get the per packet data */
  mtp2_ppd = (mtp2_ppd_t*)p_get_proto_data(wmem_file_scope(), pinfo, proto_mtp2, pinfo->fd->num);

  /* if there is no per packet data -> create it */
  if (mtp2_ppd == NULL) {
    mtp2_ppd = wmem_new(wmem_file_scope(), mtp2_ppd_t);
    /* set the proto_data_fields
     * because these are the values which we would like to see
     * if this packet is seen again */
    if (dir_state == FORWARD) {
      set_ppd_fields_based_on_convo_directon_data(mtp2_ppd, convo_data->forward);
    } else {
      set_ppd_fields_based_on_convo_directon_data(mtp2_ppd, convo_data->backward);
    }
    /* store the ppd to be able to get in the next time we see this packet */
    p_add_proto_data(wmem_file_scope(), pinfo, proto_mtp2, pinfo->fd->num, mtp2_ppd);
  }

  /* get the reass. seq num from the ppd
   * this is needed because it is modified and stored in the convo_data */
  reass_seq_num = mtp2_ppd->reass_seq_num_for_reass_check_before_fh;

  /* call the function to dissect this actual tvb */
  result = dissect_mtp2_tvb(tvb,
      mtp2_ppd->mtp2_flag_search,
      mtp2_ppd->data_buff,
      mtp2_ppd->data_buff_offset,
      mtp2_ppd->state,
      mtp2_ppd->last_flag_beginning_offset_for_align_check);

  /* if this is the first time, do the reassemble things
   * else just check for reassembled data */
  if (pinfo->fd->visited == FALSE) {
    /* if there was a flag in this tvb, the data found before the 1st flag
     * have to be treated differently than the data found after the last flag
     * this means we need to use different reass_seq_num when adding them to the reass. handler */
    if (result->flag_found == TRUE) {
      /* add the data found before the first flag with the same reass_seq_num as the
       * data found after the last flag in the previous packet in this direction */
      fragment_add_seq_next(&mtp2_reassembly_table, /* bookkeeping table */
          result->mtp2_remain_data.before_first_flag, /* tvb containing the data which was unidentified before the first flag */
          0, /* offset is 0 because this tvb contains the unidentified data only */
          pinfo,
          mtp2_ppd->reass_seq_num_for_reass_check_before_fh, /* sequence number of the fragment stream */
          NULL, /* additional data to identify the segment */
          tvb_reported_length_remaining(result->mtp2_remain_data.before_first_flag, 0), /* length is the whole tvb's length */
          FALSE); /* there are no more fragments */

      /* get a new reass seq num for the data found after the last flag */
      mtp2_ppd->reass_seq_num_for_reass_check_after_lh = reass_seq_num = get_new_reass_seq_num();

      /* if there were data found after the last flag, add it to the reass. handler with the new reass_seq_num */
      if (result->mtp2_remain_data.after_last_flag != NULL) {
        fragment_add_seq_next(&mtp2_reassembly_table, /* bookkeeping table */
            result->mtp2_remain_data.after_last_flag, /* tvb containing the data which was unidentified before the first flag */
            0, /* offset is 0 because this tvb contains the unidentified data only */
            pinfo,
            mtp2_ppd->reass_seq_num_for_reass_check_after_lh, /* sequence number of the fragment stream */
            NULL, /* additional data to identify the segment */
            tvb_reported_length_remaining(result->mtp2_remain_data.after_last_flag, 0), /* length is the whole tvb's length */
            TRUE); /* there are more fragments */
      }
    } else {
      /* here the increment of the reass_seq_num is not needed because this RTP frame was completely part
       * of an MTP2 frame beginning in the previous packet
       * this need to be added with the same reass_seq_num */
      if (result->mtp2_remain_data.after_last_flag != NULL) {
        fragment_add_seq_next(&mtp2_reassembly_table, /* bookkeeping table */
            result->mtp2_remain_data.after_last_flag, /* tvb containing the data which was unidentified before the first flag */
            0, /* offset is 0 because this tvb contains the unidentified data only */
            pinfo,
            mtp2_ppd->reass_seq_num_for_reass_check_before_fh, /* sequence number of the fragment stream */
            NULL, /* additional data to identify the segment */
            tvb_reported_length_remaining(result->mtp2_remain_data.after_last_flag, 0), /* length is the whole tvb's length */
            TRUE); /* there are more fragments */
      }
    }
    /* store the values in convo_data
     * but just in case if this packet was not seen before
     * if it was
     *   then the convo data shall not be used (contains inappropriate info for us
     *   the actual values needed for reassembly should be get from the mtp2_ppd
     *   therefore no need to set the convo data */
    /* differentiate between forward and backward directions */
    if (dir_state == FORWARD) {
      set_direction_fields_based_on_result_and_reass_seq_num(convo_data->forward, result, reass_seq_num);
    } else {
      set_direction_fields_based_on_result_and_reass_seq_num(convo_data->backward, result, reass_seq_num);
    }

  /* if the packet was seen before */
  } else {
    tvbuff_t            *new_tvb = NULL;
    fragment_head       *frag_msg_before_fh = NULL;
    fragment_head       *frag_msg_after_lh = NULL;
    gchar               *col_info_str = NULL;           /* char array to store temporary string for col info update */

    /* get the fragment data both for before first and after last flags */
    /* before first flag */
    frag_msg_before_fh = fragment_get_reassembled_id(&mtp2_reassembly_table,
        pinfo,
        mtp2_ppd->reass_seq_num_for_reass_check_before_fh);
    /* after last flag */
    frag_msg_after_lh = fragment_get_reassembled_id(&mtp2_reassembly_table,
        pinfo,
        mtp2_ppd->reass_seq_num_for_reass_check_after_lh);
    /* if there is reassembled data before the first flag */
    if (frag_msg_before_fh != NULL) {
      /* get the reassembled tvb */
      new_tvb = process_reassembled_data(result->mtp2_remain_data.before_first_flag,
          0,
          pinfo,
          (result->mtp2_remain_data.before_fh_unalignment_offset != 0
              ?"Reassembled MTP2 Packet [Unaligned]"
              :"Reassembled MTP2 Packet"),
          frag_msg_before_fh,
          &mtp2_frag_items,
          NULL,
          tree);
      /* there is reassembled data */
      if (new_tvb != NULL && tvb_reported_length_remaining(new_tvb, 0) > 0) {

        /* if there was a frame reset before the first flag */
        if (result->mtp2_remain_data.before_fh_frame_reset == TRUE) {
          /* issue frame reset */
          issue_frame_reset_info(tree, new_tvb);
          /* prepare col_info string */
          col_info_str = "[Frame Reset in reassembly]";
        } else {
          /* append the reassembled packet to the head of the packet list */
          wmem_list_prepend(result->found_packets, prepare_data_for_found_packet(new_tvb,result->mtp2_remain_data.before_fh_unalignment_offset));
          /* set the protocol name */
          col_add_str(pinfo->cinfo, COL_PROTOCOL, "MTP2");
        }
      }
    }

    /* if there were packets found */
    if (wmem_list_count(result->found_packets) != 0) {
      /* boolean variable to help to print proper col_info if unaligned packet is found */
      gboolean was_unaligned_packet = FALSE;
      /* pointer walking through the list of found packets */
      wmem_list_frame_t *recognized_packet = wmem_list_head(result->found_packets);

      /* info field pre-set, we can see the MTP2 strings even if there is an error in the dissection */
      col_add_str(pinfo->cinfo, COL_PROTOCOL, "MTP2");
      col_add_str(pinfo->cinfo, COL_INFO, "MTP2");
      /* while there are available packets */
      while (recognized_packet != NULL) {
        mtp2_recognized_packet_t *recognized_packet_data = (mtp2_recognized_packet_t *) wmem_list_frame_data(recognized_packet);
        if (recognized_packet_data->unalignment_offset == 0) {
          /* pass the data to the mtp2 dissector */
          add_new_data_source(pinfo, recognized_packet_data->data, "MTP2 packet");
          dissect_mtp2_common(recognized_packet_data->data, pinfo, tree, FALSE, use_extended_sequence_numbers_default);
        } else {
          add_new_data_source(pinfo, recognized_packet_data->data, "MTP2 packet [Unaligned]");
          issue_unaligned_info(tree, recognized_packet_data->data, recognized_packet_data->unalignment_offset);
          was_unaligned_packet = TRUE;
        }

        /* increment the pointer */
        recognized_packet = wmem_list_frame_next(recognized_packet);
      }
      /* insert how many packets were found */
      col_info_str = ws_strdup_printf("%s: %u Packet%s%s%s",
          "MTP2",
          wmem_list_count(result->found_packets),
          (wmem_list_count(result->found_packets) > 1
              ?"s"
              :""
          ),
          (was_unaligned_packet
              ?ws_strdup_printf(" [Unaligned Packet%s]", (wmem_list_count(result->found_packets)>1
                  ?"s"
                  :""))
              :""
          ),
          (col_info_str == NULL
              ?""
              :col_info_str
          )
          );
      col_add_str(pinfo->cinfo, COL_INFO, col_info_str);
      g_free(col_info_str);
    /* if there were no packets found */
    } else {
      if (tvb_reported_length_remaining(result->mtp2_remain_data.before_first_flag,0) == 0
          && result->mtp2_remain_data.after_last_flag != NULL
          && frag_msg_before_fh)
      {
        col_add_str(pinfo->cinfo, COL_PROTOCOL, "MTP2");
        col_info_str = ws_strdup_printf("[MTP2 Reassembled in: %u]", frag_msg_before_fh->reassembled_in);
        col_add_str(pinfo->cinfo, COL_INFO, col_info_str);
        g_free(col_info_str);
      } else {
        col_add_str(pinfo->cinfo, COL_PROTOCOL, "MTP2");
        col_info_str = "[MTP2 No Packets]";
        col_add_str(pinfo->cinfo, COL_INFO, col_info_str);
      }
    }
    /* this adds the "Reassembled in" text to the proto tree to the packet where there is leftover data at the end */
    process_reassembled_data(result->mtp2_remain_data.after_last_flag,
        0,
        pinfo,
        "Reassembled MTP2 Packet",
        frag_msg_after_lh,
        &mtp2_frag_items,
        NULL,
        tree);
  }

  /* the whole tvb was processed */
  return tvb_captured_length(tvb);
}

void
proto_register_mtp2(void)
{

  static hf_register_info hf[] = {
    { &hf_mtp2_bsn,       { "Backward sequence number", "mtp2.bsn",      FT_UINT8,  BASE_DEC, NULL,                    BSN_MASK,            NULL, HFILL } },
    { &hf_mtp2_ext_bsn,   { "Backward sequence number", "mtp2.bsn",      FT_UINT16, BASE_DEC, NULL,                    EXTENDED_BSN_MASK,   NULL, HFILL } },
    { &hf_mtp2_ext_res,   { "Reserved",                 "mtp2.res",      FT_UINT16, BASE_DEC, NULL,                    EXTENDED_RES_MASK,   NULL, HFILL } },
    { &hf_mtp2_bib,       { "Backward indicator bit",   "mtp2.bib",      FT_UINT8,  BASE_DEC, NULL,                    BIB_MASK,            NULL, HFILL } },
    { &hf_mtp2_ext_bib,   { "Backward indicator bit",   "mtp2.bib",      FT_UINT16, BASE_DEC, NULL,                    EXTENDED_BIB_MASK,   NULL, HFILL } },
    { &hf_mtp2_fsn,       { "Forward sequence number",  "mtp2.fsn",      FT_UINT8,  BASE_DEC, NULL,                    FSN_MASK,            NULL, HFILL } },
    { &hf_mtp2_ext_fsn,   { "Forward sequence number",  "mtp2.fsn",      FT_UINT16, BASE_DEC, NULL,                    EXTENDED_FSN_MASK,   NULL, HFILL } },
    { &hf_mtp2_fib,       { "Forward indicator bit",    "mtp2.fib",      FT_UINT8,  BASE_DEC, NULL,                    FIB_MASK,            NULL, HFILL } },
    { &hf_mtp2_ext_fib,   { "Forward indicator bit",    "mtp2.fib",      FT_UINT16, BASE_DEC, NULL,                    EXTENDED_FIB_MASK,   NULL, HFILL } },
    { &hf_mtp2_li,        { "Length Indicator",         "mtp2.li",       FT_UINT8,  BASE_DEC, NULL,                    LI_MASK,             NULL, HFILL } },
    { &hf_mtp2_ext_li,    { "Length Indicator",         "mtp2.li",       FT_UINT16, BASE_DEC, NULL,                    EXTENDED_LI_MASK,    NULL, HFILL } },
    { &hf_mtp2_spare,     { "Spare",                    "mtp2.spare",    FT_UINT8,  BASE_DEC, NULL,                    SPARE_MASK,          NULL, HFILL } },
    { &hf_mtp2_ext_spare, { "Spare",                    "mtp2.spare",    FT_UINT16, BASE_DEC, NULL,                    EXTENDED_SPARE_MASK, NULL, HFILL } },
    { &hf_mtp2_sf,        { "Status field",             "mtp2.sf",       FT_UINT8,  BASE_DEC, VALS(status_field_vals), 0x0,                 NULL, HFILL } },
    { &hf_mtp2_sf_extra,  { "Status field extra octet", "mtp2.sf_extra", FT_UINT8,  BASE_HEX, NULL,                    0x0,                 NULL, HFILL } },
    { &hf_mtp2_fcs_16,    { "FCS 16",                   "mtp2.fcs_16",   FT_UINT16, BASE_HEX, NULL,                    0x0,                 NULL, HFILL } },
    { &hf_mtp2_fcs_16_status, { "FCS 16",               "mtp2.fcs_16.status", FT_UINT8, BASE_NONE, VALS(proto_checksum_vals), 0x0,          NULL, HFILL } },
    { &hf_mtp2_unexpect_end, { "Unexpected packet end","mtp2.unexpected_end", FT_NONE, BASE_NONE, NULL,                0x0,                 NULL, HFILL } },
    { &hf_mtp2_frame_reset, { "Frame reset",           "mtp2.frame_reset", FT_NONE, BASE_NONE, NULL,                   0x0,                 NULL, HFILL } },
    /* extend header fields with the reassemble ones */
    {&hf_mtp2_fragments,
        {"Message fragments", "mtp2.msg.fragments",
        FT_NONE, BASE_NONE, NULL, 0x00, NULL, HFILL } },
    {&hf_mtp2_fragment,
        {"Message fragment", "mtp2.msg.fragment",
        FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } },
    {&hf_mtp2_fragment_overlap,
        {"Message fragment overlap", "mtp2.msg.fragment.overlap",
        FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL } },
    {&hf_mtp2_fragment_overlap_conflicts,
        {"Message fragment overlapping with conflicting data",
        "mtp2.msg.fragment.overlap.conflicts",
        FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL } },
    {&hf_mtp2_fragment_multiple_tails,
        {"Message has multiple tail fragments",
        "mtp2.msg.fragment.multiple_tails",
        FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL } },
    {&hf_mtp2_fragment_too_long_fragment,
        {"Message fragment too long", "mtp2.msg.fragment.too_long_fragment",
        FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL } },
    {&hf_mtp2_fragment_error,
        {"Message defragmentation error", "mtp2.msg.fragment.error",
        FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } },
    {&hf_mtp2_fragment_count,
        {"Message defragmentation count", "mtp2.msg.fragment.count",
        FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } },
    {&hf_mtp2_reassembled_in,
        {"Reassembled in", "mtp2.msg.reassembled.in",
        FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } },
    {&hf_mtp2_reassembled_length,
        {"Reassembled length", "mtp2.msg.reassembled.length",
        FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL } }
  };

  static gint *ett[] = {
    &ett_mtp2,
    /* extend ett with the fragment fields */
    &ett_mtp2_fragment,
    &ett_mtp2_fragments
  };

  static ei_register_info ei[] = {
     { &ei_mtp2_checksum_error, { "mtp2.checksum.error", PI_CHECKSUM, PI_WARN, "MTP2 Frame CheckFCS 16 Error", EXPFILL }},
     { &ei_mtp2_li_bad, { "mtp2.li.bad", PI_PROTOCOL, PI_WARN, "Bad length indicator value", EXPFILL }},
  };

  module_t *mtp2_module;
  expert_module_t* expert_mtp2;

  proto_mtp2 = proto_register_protocol("Message Transfer Part Level 2", "MTP2", "mtp2");
  mtp2_handle = register_dissector("mtp2", dissect_mtp2, proto_mtp2);
  register_dissector("mtp2_with_crc", dissect_mtp2_with_crc, proto_mtp2);
  mtp2_with_phdr_handle = register_dissector("mtp2_with_phdr", dissect_mtp2_with_phdr,
                                                  proto_mtp2);
  mtp2_bitstream_handle = register_dissector("mtp2_bitstream", dissect_mtp2_bitstream, proto_mtp2);

  proto_register_field_array(proto_mtp2, hf, array_length(hf));
  proto_register_subtree_array(ett, array_length(ett));
  expert_mtp2 = expert_register_protocol(proto_mtp2);
  expert_register_field_array(expert_mtp2, ei, array_length(ei));

  mtp2_module = prefs_register_protocol(proto_mtp2, NULL);
  prefs_register_bool_preference(mtp2_module,
                                 "use_extended_sequence_numbers",
                                 "Use extended sequence numbers",
                                 "Whether the MTP2 dissector should use extended sequence numbers as described in Q.703, Annex A as a default.",
                                 &use_extended_sequence_numbers_default);
  prefs_register_bool_preference(mtp2_module,
                                 "capture_contains_frame_check_sequence",
                                 "Assume packets have FCS",
                                 "Some SS7 capture hardware includes the FCS at the end of the packet, others do not.",
                                 &capture_contains_fcs_crc_default);

  /* register bool and range preferences */
  prefs_register_bool_preference(mtp2_module,
                                 "reverse_bit_order_mtp2",
                                 "Reverse bit order inside bytes",
                                 "Reverse the bit order inside bytes specified in Q.703.",
                                 &reverse_bit_order_mtp2);
  prefs_register_obsolete_preference(mtp2_module, "rtp_payload_type");

  register_init_routine(&mtp2_init_routine);
}

void
proto_reg_handoff_mtp2(void)
{
  dissector_add_uint("wtap_encap", WTAP_ENCAP_MTP2, mtp2_handle);
  dissector_add_uint("wtap_encap", WTAP_ENCAP_MTP2_WITH_PHDR,
                                   mtp2_with_phdr_handle);

  mtp3_handle   = find_dissector_add_dependency("mtp3", proto_mtp2);

  dissector_add_string("rtp_dyn_payload_type", "MTP2", mtp2_bitstream_handle);

  dissector_add_uint_range_with_preference("rtp.pt", "", mtp2_bitstream_handle);
}

/*
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
 *
 * Local Variables:
 * c-basic-offset: 2
 * tab-width: 8
 * indent-tabs-mode: nil
 * End:
 *
 * ex: set shiftwidth=2 tabstop=8 expandtab:
 * :indentSize=2:tabSize=8:noTabs=true:
 */