aboutsummaryrefslogtreecommitdiffstats
path: root/epan/dissectors/packet-hl7.c
blob: 72bfa0898780ee2e2b6114c2dbcfd9b5feb2df6d (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
/* packet-hl7.c
 * Routines for Health Level 7 (HL7) dissection: HL7 messages wrapped in
 * MLLP session layer as specified in 'HL7 Implementation Guide for HL7
 * version 2.3.1, appendix C "Lower Layer Protocols", section C.4.3.
 *
 * Copyright 2016 Francesco Fondelli <francesco dot fondelli, gmail dot com>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * TODO:
 * - HL7 messages are most commonly strings with strict ASCII encoding.
 *   However, Unicode or UTF-8 encodings are possible (?). This dissector
 *   lacks support for non-ASCII encodings.
 * - Component and sub-component expansion (not sure is necessary).
 * - Handling delimiter characters in data, i.e. escape sequences support.
 * - Add event type human readable strings.
 * - Improve heuristic detection logic: can a message start with FHS? HLLB
 *   encapsulation? Some common TCP ports besides the one assigned by IANA?
 * - Use GHashTable for lookup segment type description instead of linear
 *   search.
 */

#include "config.h"

#include <epan/packet.h>
#include <epan/addr_resolv.h>
#include <epan/conversation.h>
#include <epan/expert.h>
#include <epan/prefs.h>

#include <stdio.h>

void proto_register_hl7(void);
void proto_reg_handoff_hl7(void);

/* 2575 is registered at IANA for HL7 */
#define TCP_PORT_HL7 2575
#define LLP_SOB 0x0B /* Start Of Block byte */
#define LLP_EOB 0x1C0D /* End Of Block byte + \r */

struct msh {                    // typical/default values
    char field_separator;       // '|'
    char component_separator;   // '^'
    char repetition_separator;  // '~'
    char escape_character;      // '\'
    char subcomponent_separator;// '&'
    char message_type[4];
    char trigger_event[4];
};

dissector_handle_t hl7_handle;
dissector_handle_t hl7_heur_handle;

static int proto_hl7 = -1;

static gint hf_hl7_raw = -1;
static gint hf_hl7_raw_segment = -1;
static gint hf_hl7_llp_sob = -1;
static gint hf_hl7_llp_eob = -1;
static gint hf_hl7_message_type = -1;
static gint hf_hl7_event_type = -1;
static gint hf_hl7_segment = -1;
static gint hf_hl7_field = -1;

static gint ett_hl7 = -1;
static gint ett_hl7_segment = -1;

static expert_field ei_hl7_malformed = EI_INIT;

/* FF: global_hl7_raw determines whether we are going to display
 * the raw text of the HL7 message (like SIP and MEGACO dissectors) */
static gboolean global_hl7_raw = FALSE;

/* FF: global_hl7_llp determines whether we are going to display
 * the LLP block markers */
static gboolean global_hl7_llp = FALSE;

/* as per Health Level Seven, Version 2.6, appendix A */
static const string_string hl7_msg_type_vals[] = {
    { "ACK", "General acknowledgment" },
    { "ADT", "Admit Discharge Transfer" },
    { "BAR", "Add/change billing account" },
    { "BPS", "Blood product dispense status" },
    { "BRP", "Blood product dispense status acknowledgement" },
    { "BRT", "Blood product transfusion/disposition acknowledgement" },
    { "BTS", "Blood product transfusion/disposition" },
    { "CRM", "Clinical study registration" },
    { "CSU", "Unsolicited study data" },
    { "DFT", "Detail financial transactions" },
    { "EAC", "Automated equipment command" },
    { "EAN", "Automated equipment notification" },
    { "EAR", "Automated equipment response" },
    { "EHC", "Health Care Invoice" },
    { "ESR", "Automated equipment status update acknowledgment" },
    { "ESU", "Automated equipment status update" },
    { "INR", "Automated equipment inventory request" },
    { "INU", "Automated equipment inventory update" },
    { "LSR", "Automated equipment log/service request" },
    { "LSU", "Automated equipment log/service update" },
    { "MDM", "Medical document management" },
    { "MFN", "Master files notification" },
    { "NMD", "Application management data" },
    { "NMQ", "Application management query" },
    { "OMB", "Blood product order" },
    { "OMD", "Dietary order" },
    { "OMG", "General clinical order" },
    { "OMI", "Imaging order" },
    { "OML", "Laboratory order" },
    { "OMN", "Non-stock requisition order" },
    { "OMP", "Pharmacy/treatment order" },
    { "OMS", "Stock requisition order" },
    { "OPL", "Population/Location-Based Laboratory Order" },
    { "OPR", "Population/Location-Based Laboratory Order Acknowledgment" },
    { "OPU", "Unsolicited Population/Location-Based Laboratory Observation" },
    { "ORB", "Blood product order acknowledgement" },
    { "ORD", "Dietary order acknowledgment" },
    { "ORF", "Query for results of observation" },
    { "ORG", "General clinical order acknowledgment" },
    { "ORI", "Imaging order acknowledgement" },
    { "ORL", "Laboratory acknowledgment (unsolicited)" },
    { "ORM", "Pharmacy/treatment order" },
    { "ORN", "Non-stock requisition - General order acknowledgment" },
    { "ORP", "Pharmacy/treatment order acknowledgment" },
    { "ORR", "General order response response to any ORM" },
    { "ORS", "Stock requisition - Order acknowledgment" },
    { "ORU", "Unsolicited transmission of an observation" },
    { "OSQ", "Query response for order status" },
    { "OUL", "Unsolicited laboratory observation" },
    { "PEX", "Product experience" },
    { "PGL", "Patient goal" },
    { "PIN", "Patient insurance information" },
    { "PMU", "Add personnel record" },
    { "PPG", "Patient pathway (goal-oriented)" },
    { "PPP", "Patient pathway (problem-oriented)" },
    { "PPR", "Patient problem" },
    { "PPT", "Patient pathway goal-oriented response" },
    { "PPV", "Patient goal response" },
    { "PRR", "Patient problem response" },
    { "PTR", "Patient pathway problem-oriented response" },
    { "QBP", "Query by parameter" },
    { "QCN", "Cancel query" },
    { "QRY", "Query, original mode" },
    { "QSB", "Create subscription" },
    { "QSX", "Cancel subscription/acknowledge" },
    { "QVR", "Query for previous events" },
    { "RAR", "Pharmacy/treatment administration information" },
    { "RAS", "Pharmacy/treatment administration" },
    { "RDE", "Pharmacy/treatment encoded order" },
    { "RDS", "Pharmacy/treatment dispense" },
    { "RDY", "Display based response" },
    { "REF", "Patient referral" },
    { "RER", "Pharmacy/treatment encoded order information" },
    { "RGV", "Pharmacy/treatment give" },
    { "ROR", "Pharmacy/treatment order response" },
    { "RQA", "Request patient authorization" },
    { "RQC", "Request clinical information" },
    { "RQI", "Request patient information" },
    { "RQP", "Request patient demographics" },
    { "RRA", "Pharmacy/treatment administration acknowledgment" },
    { "RRD", "Pharmacy/treatment dispense acknowledgment" },
    { "RRE", "Pharmacy/treatment encoded order acknowledgment" },
    { "RRG", "Pharmacy/treatment give acknowledgment" },
    { "RSP", "Segment pattern response" },
    { "RTB", "Tabular response" },
    { "SCN", "Notification of Anti-Microbial Device Cycle Data" },
    { "SDN", "Notification of Anti-Microbial Device Data" },
    { "SDR", "Sterilization anti-microbial device data request" },
    { "SIU", "Schedule information unsolicited" },
    { "SLN", "Notification of New Sterilization Lot" },
    { "SLR", "Sterilization lot request" },
    { "SMD", "Sterilization anti-microbial device cycle data request" },
    { "SQM", "Schedule query" },
    { "SRM", "Schedule request" },
    { "SSR", "Specimen status request" },
    { "SSU", "Specimen status update" },
    { "STC", "Notification of Sterilization Configuration" },
    { "STI", "Sterilization item request" },
    { "SUR", "Summary product experience report" },
    { "TCR", "Automated equipment test code settings request" },
    { "TCU", "Automated equipment test code settings update" },
    { "VXQ", "Query for vaccination record" },
    { "VXR", "Vaccination record response" },
    { "VXU", "Unsolicited vaccination record update" },
    { "VXX", "Response for vaccination query with multiple PID matches" },
    { NULL, NULL }
};

/* as per Health Level Seven, Version 2.6, appendix A */
static const string_string hl7_seg_type_vals[] = {
    { "ABS", "Abstract" },
    { "ACC", "Accident" },
    { "ADD", "Addendum" },
    { "ADJ", "Adjustment" },
    { "AFF", "Professional Affiliation" },
    { "AIG", "Appointment Information - General Resource" },
    { "AIL", "Appointment Information - Location Resource" },
    { "AIP", "Appointment Information - Personnel Resource" },
    { "AIS", "Appointment Information" },
    { "AL1", "Patient Allergy Information" },
    { "APR", "Appointment Preferences" },
    { "ARQ", "Appointment Request" },
    { "ARV", "Access Restriction" },
    { "AUT", "Authorization Information" },
    { "BHS", "Batch Header" },
    { "BLC", "Blood Code" },
    { "BLG", "Billing" },
    { "BPO", "Blood product order" },
    { "BPX", "Blood product dispense status" },
    { "BTS", "Batch Trailer" },
    { "BTX", "Blood Product Transfusion/Disposition" },
    { "CDM", "Charge Description Master" },
    { "CER", "Certificate Detail" },
    { "CM0", "Clinical Study Master" },
    { "CM1", "Clinical Study Phase Master" },
    { "CM2", "Clinical Study Schedule Master" },
    { "CNS", "Clear Notification" },
    { "CON", "Consent Segment" },
    { "CSP", "Clinical Study Phase" },
    { "CSR", "Clinical Study Registration" },
    { "CSS", "Clinical Study Data Schedule Segment" },
    { "CTD", "Contact Data" },
    { "CTI", "Clinical Trial Identification" },
    { "DB1", "Disability" },
    { "DG1", "Diagnosis" },
    { "DMI", "DRG Master File Information" },
    { "DRG", "Diagnosis Related Group" },
    { "DSC", "Continuation Pointer" },
    { "DSP", "Display Data" },
    { "ECD", "Equipment Command" },
    { "ECR", "Equipment Command Response" },
    { "EDE", "Encapsulated Data (wrong segment)" },
    { "EDU", "Educational Detail" },
    { "EQP", "Equipment/log Service" },
    { "EQU", "Equipment Detail" },
    { "ERR", "Error" },
    { "EVN", "Event Type" },
    { "FAC", "Facility" },
    { "FHS", "File Header" },
    { "FT1", "Financial Transaction" },
    { "FTS", "File Trailer" },
    { "GOL", "Goal Detail" },
    { "GP1", "Grouping/Reimbursement - Visit" },
    { "GP2", "Grouping/Reimbursement - Procedure Line Item" },
    { "GT1", "Guarantor" },
    { "IAM", "Patient Adverse Reaction Information" },
    { "IIM", "Inventory Item Master" },
    { "ILT", "Material Lot" },
    { "IN1", "Insurance" },
    { "IN2", "Insurance Additional Information" },
    { "IN3", "Insurance Additional Information, Certification" },
    { "INV", "Inventory Detail" },
    { "IPC", "Imaging Procedure Control Segment" },
    { "IPR", "Invoice Processing Results" },
    { "ISD", "Interaction Status Detail" },
    { "ITM", "Material Item" },
    { "IVC", "Invoice Segment" },
    { "IVT", "Material Location" },
    { "LAN", "Language Detail" },
    { "LCC", "Location Charge Code" },
    { "LCH", "Location Characteristic" },
    { "LDP", "Location Department" },
    { "LOC", "Location Identification" },
    { "LRL", "Location Relationship" },
    { "MFA", "Master File Acknowledgment" },
    { "MFE", "Master File Entry" },
    { "MFI", "Master File Identification" },
    { "MRG", "Merge Patient Information" },
    { "MSA", "Message Acknowledgment" },
    { "MSH", "Message Header" },
    { "NCK", "System Clock" },
    { "NDS", "Notification Detail" },
    { "NK1", "Next of Kin - Associated Parties" },
    { "NPU", "Bed Status Update" },
    { "NSC", "Application Status Change" },
    { "NST", "Application control level statistics" },
    { "NTE", "Notes and Comments" },
    { "OBR", "Observation Request" },
    { "OBX", "Observation/Result" },
    { "ODS", "Dietary Orders, Supplements, and Preferences" },
    { "ODT", "Diet Tray Instructions" },
    { "OM1", "General Segment" },
    { "OM2", "Numeric Observation" },
    { "OM3", "Categorical Service/Test/Observation" },
    { "OM4", "Observations that Require Specimens" },
    { "OM5", "Observation Batteries (Sets)" },
    { "OM6", "Observations that are Calculated from Other" },
    { "OM7", "Additional Basic Attributes" },
    { "ORC", "Common Order" },
    { "ORG", "Practitioner Organization Unit" },
    { "OVR", "Override Segment" },
    { "PCE", "Patient Charge Cost Center Exceptions" },
    { "PCR", "Possible Causal Relationship" },
    { "PD1", "Patient Additional Demographic" },
    { "PDA", "Patient Death and Autopsy" },
    { "PDC", "Product Detail Country" },
    { "PEO", "Product Experience Observation" },
    { "PES", "Product Experience Sender" },
    { "PID", "Patient Identification" },
    { "PKG", "Item Packaging" },
    { "PMT", "Payment Information" },
    { "PR1", "Procedures" },
    { "PRA", "Practitioner Detail" },
    { "PRB", "Problem Details" },
    { "PRC", "Pricing" },
    { "PRD", "Provider Data" },
    { "PSG", "Product/Service Group" },
    { "PSH", "Product Summary Header" },
    { "PSL", "Product/Service Line Item" },
    { "PSS", "Product/Service Section" },
    { "PTH", "Pathway" },
    { "PV1", "Patient Visit" },
    { "PV2", "Patient Visit - Additional Information" },
    { "PYE", "Payee Information" },
    { "QAK", "Query Acknowledgment" },
    { "QID", "Query Identification" },
    { "QPD", "Query Parameter Definition" },
    { "QRD", "Original-Style Query Definition" },
    { "QRF", "Original style query filter" },
    { "QRI", "Query Response Instance" },
    { "RCP", "Response Control Parameter" },
    { "RDF", "Table Row Definition" },
    { "RDT", "Table Row Data" },
    { "REL", "Clinical Relationship Segment" },
    { "RF1", "Referral Information" },
    { "RFI", "Request for Information" },
    { "RGS", "Resource Group" },
    { "RMI", "Risk Management Incident" },
    { "ROL", "Role" },
    { "RQ1", "Requisition Detail-1" },
    { "RQD", "Requisition Detail" },
    { "RXA", "Pharmacy/Treatment Administration" },
    { "RXC", "Pharmacy/Treatment Component Order" },
    { "RXD", "Pharmacy/Treatment Dispense" },
    { "RXE", "Pharmacy/Treatment Encoded Order" },
    { "RXG", "Pharmacy/Treatment Give" },
    { "RXO", "Pharmacy/Treatment Order" },
    { "RXR", "Pharmacy/Treatment Route" },
    { "SAC", "Specimen Container detail" },
    { "SCD", "Anti-Microbial Cycle Data" },
    { "SCH", "Scheduling Activity Information" },
    { "SCP", "Sterilizer Configuration Notification (Anti-Microbial Devices)" },
    { "SDD", "Sterilization Device Data" },
    { "SFT", "Software Segment" },
    { "SID", "Substance Identifier" },
    { "SLT", "Sterilization Lot" },
    { "SPM", "Specimen" },
    { "STF", "Staff Identification" },
    { "STZ", "Sterilization Parameter" },
    { "TCC", "Test Code Configuration" },
    { "TCD", "Test Code Detail" },
    { "TQ1", "Timing/Quantity" },
    { "TQ2", "Timing/Quantity Relationship" },
    { "TXA", "Transcription Document Header" },
    { "UAC", "User Authentication Credential Segment" },
    { "UB1", "UB82" },
    { "UB2", "UB92 Data" },
    { "URD", "Results/update Definition" },
    { "URS", "Unsolicited Selection" },
    { "VAR", "Variance" },
    { "VND", "Purchasing Vendor" },
    { NULL, NULL }
};

/* as per Health Level Seven, Version 2.6, appendix A */
static const string_string hl7_event_type_vals[] = {
    { "A01", "Admit/visit notification" },
    { "A02", "Transfer a patient" },
    { "A03", "Discharge/end visit" },
    { "A04", "Register a patient" },
    { "A05", "Pre-admit a patient" },
    { "A06", "Change an outpatient to an inpatient" },
    { "A07", "Change an inpatient to an outpatient" },
    { "A08", "Update patient information" },
    { "A09", "Patient departing - tracking" },
    { "A10", "Patient arriving - tracking" },
    { "A11", "Cancel admit/visit notification" },
    { "A12", "Cancel transfer" },
    { "A13", "Cancel discharge/end visit" },
    { "A14", "Pending admit" },
    { "A15", "Pending transfer" },
    { "A16", "Pending discharge" },
    { "A17", "Swap patients" },
    { "A18", "Merge patient information" },
    { "A19", "Patient query" },
    { "A20", "Bed status update" },
    { "A21", "Patient goes on a \"leave of absence\"" },
    { "A22", "Patient returns from a \"leave of absence\"" },
    { "A23", "Delete a patient record" },
    { "A24", "Link patient information" },
    { "A25", "Cancel pending discharge " },
    { "A26", "Cancel pending transfer" },
    { "A27", "Cancel pending admit" },
    { "A28", "Add person information" },
    { "A29", "Delete person information" },
    { "A30", "Merge person information" },
    { "A31", "Update person information" },
    { "A32", "Cancel patient arriving" },
    { "A33", "Cancel patient departing" },
    { "A34", "Merge patient information - patient ID only" },
    { "A35", "Merge patient information - account number only" },
    { "A36", "Merge patient information - patient ID and account number" },
    { "A37", "Unlink patient information" },
    { "A38", "Cancel pre-admit" },
    { "A39", "Merge person - patient ID" },
    { "A40", "Merge patient - patient identifier list" },
    { "A41", "Merge account - patient account number" },
    { "A42", "Merge visit - visit number" },
    { "A43", "Move patient information - patient identifier list" },
    { "A44", "Move account information - patient account number" },
    { "A45", "Move visit information - visit number" },
    { "A46", "Change patient ID" },
    { "A47", "Change patient identifier list" },
    { "A48", "Change alternate patient ID" },
    { "A49", "Change patient account number" },
    { "A50", "Change visit number" },
    { "A51", "Change alternate visit ID" },
    { "A52", "Cancel leave of absence for a patient" },
    { "A53", "Cancel patient returns from a leave of absence" },
    { "A54", "Change attending doctor" },
    { "A55", "Cancel change attending doctor" },
    { "A60", "Update allergy information" },
    { "A61", "Change consulting doctor" },
    { "A62", "Cancel change consulting doctor" },
    { "B01", "Add personnel record" },
    { "B02", "Update personnel record" },
    { "B03", "Delete personnel re cord" },
    { "B04", "Active practicing person" },
    { "B05", "Deactivate practicing person" },
    { "B06", "Terminate practicing person" },
    { "B07", "Grant Certificate/Permission" },
    { "B08", "Revoke Certificate/Permission" },
    { "C01", "Register a patient on a clinical trial" },
    { "C02", "Cancel a patient registration on clinical trial" },
    { "C03", "Correct/update registration information" },
    { "C04", "Patient has gone off a clinical trial" },
    { "C05", "Patient enters phase of clinical trial" },
    { "C06", "Cancel patient entering a phase" },
    { "C07", "Correct/update phase information" },
    { "C08", "Patient has gone off phase of clinical trial" },
    { "C09", "Automated time intervals for reporting" },
    { "C10", "Patient completes the clinical trial" },
    { "C11", "Patient completes a phase of the clinical trial" },
    { "C12", "Update/correction of patient order/result information" },
    { "E01", "Submit HealthCare Services Invoice" },
    { "E02", "Cancel HealthCare Services Invoice" },
    { "E03", "HealthCare Services Invoice Status" },
    { "E04", "Re-Assess HealthCare Services Invoice Request" },
    { "E10", "Edit/Adjudication Results" },
    { "E12", "Request Additional Information" },
    { "E13", "Additional Information Response" },
    { "E15", "Payment/Remittance Advice" },
    { "E20", "Submit Authorization Request" },
    { "E21", "Cancel Authorization Request" },
    { "E22", "Authorization Request Status" },
    { "E24", "Authorization Response " },
    { "E30", "Submit Health Document related to Authorization Request" },
    { "E31", "Cancel Health Document related to Authorization Request" },
    { "I01", "Request for insurance information" },
    { "I02", "Request/receipt of patient selection display list" },
    { "I03", "Request/receipt of patient selection list" },
    { "I04", "Request for patient demographic data" },
    { "I05", "Request for patient clinical information" },
    { "I06", "Request/receipt of clinical data listing" },
    { "I07", "Unsolicited insurance information" },
    { "I08", "Request for treatment authorization information" },
    { "I09", "Request for modification to an authorization" },
    { "I10", "Request for resubmission of an authorization" },
    { "I11", "Request for cancellation of an authorization" },
    { "I12", "Patient referral" },
    { "I13", "Modify patient referral" },
    { "I14", "Cancel patient referral" },
    { "I15", "Request patient referral status" },
    { "J01", "Cancel query/acknowledge message" },
    { "J02", "Cancel subscription/acknowledge message" },
    { "K11", "Segment pattern response in response to QBP^Q11" },
    { "K13", "Tabular response in response to QBP^Q13" },
    { "K15", "Display response in response to QBP^Q15" },
    { "K21", "Get person demographics response" },
    { "K22", "Find candidates response" },
    { "K23", "Get corresponding identifiers response" },
    { "K24", "Allocate identifiers response" },
    { "K25", "Personnel Information by Segment Response" },
    { "K31", "Dispense History Response" },
    { "M01", "Master file not otherwise specified" },
    { "M02", "Master file - staff practitioner " },
    { "M03", "Master file - test/observation" },
    { "M04", "Master files charge description" },
    { "M05", "Patient location master file" },
    { "M06", "Clinical study with phases and schedules master file" },
    { "M07", "Clinical study without phases but with schedules master file" },
    { "M08", "Test/observation (numeric) master file" },
    { "M09", "Test/observation (categorical) master file" },
    { "M10", "Test /observation batteries master file" },
    { "M11", "Test/calculated observations master file" },
    { "M12", "Master file notification message" },
    { "M13", "Master file notification - general" },
    { "M14", "Master file notification - site defined" },
    { "M15", "Inventory item master file notification" },
    { "M16", "Master File Notification Inventory Item Enhanced" },
    { "M17", "Master File Message" },
    { "N01", "Application management query message" },
    { "N02", "Application management data message (unsolicited)" },
    { "O01", "Order message" },
    { "O02", "Order response" },
    { "O03", "Diet order" },
    { "O04", "Diet order acknowledgment" },
    { "O05", "Stock requisition order" },
    { "O06", "Stock requisition acknowledgment" },
    { "O07", "Non-stock requisition order" },
    { "O08", "Non-stock requisition acknowledgment" },
    { "O09", "Pharmacy/treatment order" },
    { "O10", "Pharmacy/treatment order acknowledgment" },
    { "O11", "Pharmacy/treatment encoded order" },
    { "O12", "Pharmacy/treatment encoded order acknowledgment " },
    { "O13", "Pharmacy/treatment dispense" },
    { "O14", "Pharmacy/treatment dispense acknowledgment" },
    { "O15", "Pharmacy/treatment give" },
    { "O16", "Pharmacy/treatment give acknowledgment" },
    { "O17", "Pharmacy/treatment administration" },
    { "O18", "Pharmacy/treatment administration acknowledgment" },
    { "O19", "General clinical order" },
    { "O20", "General clinical order response" },
    { "O21", "Laboratory order" },
    { "O22", "General laboratory order response message to any OML" },
    { "O23", "Imaging order" },
    { "O24", "Imaging order response message to any OMI" },
    { "O25", "Pharmacy/treatment refill authorization request" },
    { "O26", "Pharmacy/Treatment Refill Authorization Acknowledgement" },
    { "O27", "Blood product order" },
    { "O28", "Blood product order acknowledgment" },
    { "O29", "Blood product dispense status" },
    { "O30", "Blood product dispense status acknowledgment" },
    { "O31", "Blood product transfusion/disposition" },
    { "O32", "Blood product transfusion/disposition acknowledgment" },
    { "O33", "Laboratory order for multiple orders related to a single specimen" },
    { "O34", "Laboratory order response message to a multiple order related to single specimen OML" },
    { "O35", "Laboratory order for multiple orders related to a single container of a specimen" },
    { "O36", "Laboratory order response message to a single container of a specimen OML" },
    { "O37", "Population/Location-Based Laboratory Order Message" },
    { "O38", "Population/Location-Based Laboratory Order Acknowledgment Message" },
    { "P01", "Add patient accounts" },
    { "P02", "Purge patient accounts" },
    { "P03", "Post detail financial transaction " },
    { "P04", "Generate bill and A/R statements" },
    { "P05", "Update account" },
    { "P06", "End account" },
    { "P07", "Unsolicited initial individual product experience report" },
    { "P08", "Unsolicited update individual product experience report" },
    { "P09", "Summary product experience report" },
    { "P10", "Transmit Ambulatory Payment Classification" },
    { "P11", "Post Detail Financial Transactions" },
    { "P12", "Update Diagnosis/Procedure" },
    { "PC1", "PC/problem add" },
    { "PC2", "PC/problem update" },
    { "PC3", "PC/problem delete" },
    { "PC4", "PC/problem query" },
    { "PC5", "PC/problem response" },
    { "PC6", "PC/goal add" },
    { "PC7", "PC/goal update" },
    { "PC8", "PC/goal delete" },
    { "PC9", "PC/goal query" },
    { "PCA", "PC/goal response" },
    { "PCB", "PC/pathway (problem-oriented) add" },
    { "PCC", "PC/pathway (problem-oriented) update" },
    { "PCD", "PC/pathway (problem-oriented) delete" },
    { "PCE", "PC/pathway (problem-oriented) query" },
    { "PCF", "PC/pathway (problem-oriented) query response" },
    { "PCG", "PC/pathway (goal-oriented) add" },
    { "PCH", "PC/pathway (goal-oriented) update" },
    { "PCJ", "PC/pathway (goal-oriented) delete" },
    { "PCK", "PC/pathway (goal-oriented) query" },
    { "PCL", "PC/pathway (goal-oriented) query response" },
    { "Q01", "Query sent for immediate response" },
    { "Q02", "Query sent for deferred response" },
    { "Q03", "Deferred response to a query" },
    { "Q05", "Unsolicited display update message" },
    { "Q06", "Query for order status" },
    { "Q11", "Query by parameter requesting an RSP segment pattern response" },
    { "Q13", "Query by parameter requesting an RTB tabular response" },
    { "Q15", "Query by parameter requesting an RDY display response" },
    { "Q16", "Create subscription" },
    { "Q17", "Query for previous events" },
    { "Q21", "Get person demographics" },
    { "Q22", "Find candidates" },
    { "Q23", "Get corresponding identifiers" },
    { "Q24", "Allocate identifiers" },
    { "Q25", "Personnel Information by Segment Query" },
    { "Q26", "Pharmacy/treatment order response" },
    { "Q27", "Pharmacy/treatment administration information" },
    { "Q28", "Pharmacy/treatment dispense information" },
    { "Q29", "Pharmacy/treatment encoded order information" },
    { "Q30", "Pharmacy/treatment dose information" },
    { "Q31", "Query Dispense history" },
    { "R01", "Unsolicited transmission of an observation message" },
    { "R02", "Query for results of observation" },
    { "R04", "Response to query; transmission of requested observation" },
    { "R21", "Unsolicited laboratory observation" },
    { "R22", "Unsolicited Specimen Oriented Observation Message" },
    { "R23", "Unsolicited Specimen Container Oriented Observation Message" },
    { "R24", "Unsolicited Order Oriented Observation Message" },
    { "R25", "Unsolicited Population/Location-Based Laboratory Observation Message" },
    { "R30", "Unsolicited Point-Of-Care Observation Message Without Existing Order - Place An Order" },
    { "R31", "Unsolicited New Point-Of-Care Observation Message - Search For An Order" },
    { "R32", "Unsolicited Pre-Ordered Point-Of-Care Observation" },
    { "ROR", "Pharmacy prescription order query response" },
    { "S01", "Request new appointment booking" },
    { "S02", "Request appointment rescheduling" },
    { "S03", "Request appointment modification" },
    { "S04", "Request appointment cancellation" },
    { "S05", "Request appointment discontinuation" },
    { "S06", "Request appointment deletion" },
    { "S07", "Request addition of service/resource on appointment" },
    { "S08", "Request modification of service/resource on appointment" },
    { "S09", "Request cancellation of service/resource on appointment" },
    { "S10", "Request discontinuation of service/resource on appointment" },
    { "S11", "Request deletion of service/resource on appointment" },
    { "S12", "Notification of new appointment booking" },
    { "S13", "Notification of appointment rescheduling" },
    { "S14", "Notification of appointment modification" },
    { "S15", "Notification of appointment cancellation" },
    { "S16", "Notification of appointment discontinuation" },
    { "S17", "Notification of appointment deletion" },
    { "S18", "Notification of addition of service/resource on appointment" },
    { "S19", "Notification of modification of service/resource on appointment" },
    { "S20", "Notification of cancellation of service/resource on appointment" },
    { "S21", "Notification of discontinuation of service/resource on appointment" },
    { "S22", "Notification of deletion of service/resource on appointment" },
    { "S23", "Notification of blocked schedule time slot(s)" },
    { "S24", "Notification of opened (\"unblocked\") schedule time slot(s)" },
    { "S25", "Schedule query message and response" },
    { "S26", "Notification that patient did not show up for schedule appointment" },
    { "S28", "Request new sterilization lot " },
    { "S29", "Request Sterilization lot deletion" },
    { "S30", "Request item" },
    { "S31", "Request anti-microbial device data" },
    { "S32", "Request anti-microbial device cycle data" },
    { "S33", "Notification of sterilization configuration" },
    { "S34", "Notification of sterilization lot" },
    { "S35", "Notification of sterilization lot deletion" },
    { "S36", "Notification of anti-microbial device data" },
    { "S37", "Notification of anti-microbial device cycle data" },
    { "T01", "Original document notification" },
    { "T02", "Original document notification and content" },
    { "T03", "Document status change notification" },
    { "T04", "Document status change notification and content" },
    { "T05", "Document addendum notification" },
    { "T06", "Document addendum notification and content" },
    { "T07", "Document edit notification" },
    { "T08", "Document edit notification and content" },
    { "T09", "Document replacement notification" },
    { "T10", "Document replacement notification and content" },
    { "T11", "Document cancel notification" },
    { "T12", "Document query" },
    { "U01", "Automated equipment status update" },
    { "U02", "Automated equipment status request" },
    { "U03", "Specimen status update" },
    { "U04", "specimen status request" },
    { "U05", "Automated equipment inventory update" },
    { "U06", "Automated equipment inventory request" },
    { "U07", "Automated equipment command" },
    { "U08", "Automated equipment response" },
    { "U09", "Automated equipment notification " },
    { "U10", "Automated equipment test code settings update" },
    { "U11", "Automated equipment test code settings request" },
    { "U12", "Automated equipment log/service update" },
    { "U13", "Automated equipment log/service request" },
    { "V01", "Query for vaccination record" },
    { "V02", "Response to vaccination query returning multiple PID matches" },
    { "V03", "Vaccination record response" },
    { "V04", "Unsolicited vaccination record update" },
    { "W01", "Waveform result, unsolicited transmission of requested information" },
    { "W02", "Waveform result, response to query " },
    { NULL, NULL }
};

static gboolean
event_present(const struct msh *msh) {
    return msh->trigger_event[0] == 0 ? FALSE : TRUE;
}

static int
parse_msh(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, gint offset,
          struct msh *msh)
{
    gint segment_len = -1;
    gint end_of_segment_offset = -1;
    gint field_separator_offset = -1;
    gint field_number = 0;

    /* initialize msh */
    msh->trigger_event[0] ='\0';
    msh->message_type[0] = '\0';

    /* e.g. MSH|^~\&|||||||XZY^IJK|||||||\r */
    field_number = 1;
    offset += 3; // skip 'MSH'
    msh->field_separator = tvb_get_guint8(tvb, offset);
    offset += 1;
    msh->component_separator = tvb_get_guint8(tvb, offset);
    offset += 1;
    msh->repetition_separator = tvb_get_guint8(tvb, offset);
    offset += 1;
    msh->escape_character = tvb_get_guint8(tvb, offset);
    offset += 1;
    msh->subcomponent_separator = tvb_get_guint8(tvb, offset);
    offset += 1;
    field_number++;

    /* FF: even if HL7 2.3.1 says each segment must be terminated with CR
     * we look either for a CR or an LF or both (I did find a system out
     * there that uses both) */
    segment_len = tvb_find_line_end(tvb, offset, -1, NULL, TRUE);
    if (segment_len == -1) {
        expert_add_info_format(pinfo, NULL, &ei_hl7_malformed,
                               "Segments must be terminated with CR");
        return -1;
    }
    end_of_segment_offset = offset + segment_len;

    while (offset < end_of_segment_offset) {
        field_separator_offset =
            tvb_find_guint8(tvb, offset, end_of_segment_offset - offset,
                            msh->field_separator);
        if (field_separator_offset == -1) {
            if (field_number < 9) {
                expert_add_info_format(pinfo, NULL, &ei_hl7_malformed,
                                       "MSH must have at least 9 fields");
                return -1;
            }
            return 0;
        }
        field_number++;
        offset = field_separator_offset + 1;
        if (tvb_get_guint8(tvb, offset) == msh->field_separator) {
            /* skip the empty field '||' */
            continue;
        }
        if (field_number == 9) { /* 9th field is the message type[^event] */
            msh->message_type[0] = tvb_get_guint8(tvb, offset);
            msh->message_type[1] = tvb_get_guint8(tvb, offset + 1);
            msh->message_type[2] = tvb_get_guint8(tvb, offset + 2);
            msh->message_type[3] = '\0';
            if (tree) {
                proto_item *hidden_item;
                hidden_item = proto_tree_add_item(tree, hf_hl7_message_type,
                                                  tvb, offset, 3,
                                                  ENC_ASCII|ENC_NA);
                PROTO_ITEM_SET_HIDDEN(hidden_item);
            }
            if (tvb_get_guint8(tvb, offset + 3) == msh->component_separator) {
                msh->trigger_event[0] = tvb_get_guint8(tvb, offset + 4);
                msh->trigger_event[1] = tvb_get_guint8(tvb, offset + 5);
                msh->trigger_event[2] = tvb_get_guint8(tvb, offset + 6);
                msh->trigger_event[3] = '\0';
                if (tree) {
                    proto_item *hidden_item;
                    hidden_item = proto_tree_add_item(tree, hf_hl7_event_type,
                                                      tvb, offset + 4, 3,
                                                      ENC_ASCII|ENC_NA);
                    PROTO_ITEM_SET_HIDDEN(hidden_item);
                }
            }
        }
    }
    return 0;
}

static void
dissect_hl7_segment(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree _U_,
                    gint offset, gint segment_len, gint segment_len_crlf _U_,
                    const struct msh *msh _U_)
{
    /* segment layout xyz|a|b||||c|d\rxyz|a|b|c||||d... */
    proto_tree *segment_tree = NULL;
    proto_item *ti = NULL;
    char *field_str = NULL;
    gint end_of_segment_offset = 0;
    gint field_separator_offset = 0;
    gint field_num = 0;
    gint field_len = 0;
    gint segment_consumed = 0;
    gboolean last_field = FALSE;

    /* calculate where the segment ends */
    end_of_segment_offset = offset + segment_len;

    /* iterate over any fields */
    while (offset < end_of_segment_offset) {

        field_num++;

        /* get next '|' offset */
        field_separator_offset =
            tvb_find_guint8(tvb, offset,
                            segment_len - segment_consumed,
                            msh->field_separator);

        if (field_separator_offset == -1) {
            /* we do not have a field separator */
            if (segment_consumed != segment_len) {
                /* this is the last field */
                last_field = TRUE;
                field_len = segment_len - segment_consumed;
                segment_consumed += field_len + 1;
            } else {
                /* end of tvb or reached maxlen (i.e. end of segment) */
                return;
            }
        } else {
            /* we have a field separator */
            /* calc field length and the amount of segment data consumed */
            field_len = field_separator_offset - offset;
            segment_consumed += field_len + 1;
        }

        /* skip empty fields '||' */
        if (field_len == 0) {
            /* move the offset after the separator, pointing to the next field */
            offset = field_separator_offset + 1;
            continue;
        }

        /* process the field (the 1st one generate a node in the tree view) */
        if (field_num == 1) {
            char *segment_type_id = NULL;
            segment_type_id = tvb_get_string_enc(wmem_packet_scope(),
                                                 tvb, offset, 3, ENC_ASCII);
            ti = proto_tree_add_item(tree, hf_hl7_segment,
                                     tvb, offset, segment_len_crlf,
                                     ENC_ASCII|ENC_NA);
            proto_item_set_text(ti, "%s (%s)", segment_type_id,
                                str_to_str(segment_type_id, hl7_seg_type_vals,
                                           "Unknown Segment"));
            segment_tree = proto_item_add_subtree(ti, ett_hl7_segment);
            if (global_hl7_raw) {
                proto_tree_add_item(segment_tree, hf_hl7_raw_segment, tvb, offset,
                                    segment_len_crlf, ENC_ASCII|ENC_NA);
            }
        }
        field_str = tvb_get_string_enc(wmem_packet_scope(),
                                       tvb, offset, field_len, ENC_ASCII);
        ti = proto_tree_add_item(segment_tree, hf_hl7_field,
                                 tvb, offset, field_len, ENC_ASCII|ENC_NA);
        proto_item_set_text(ti, "field %d: %s", field_num, field_str);

        /* if this is the last field we are done */
        if (last_field) {
            return;
        }

        /* move the offset after the separator, pointing to the next field */
        offset = field_separator_offset + 1;
    }
}

static void
dissect_hl7_message(tvbuff_t *tvb, guint tvb_offset, gint len,
                    packet_info *pinfo, proto_tree *tree, void *data _U_)
{
    guint offset = tvb_offset;
    guint sob_offset = offset;
    guint eob_offset = offset + len - 2;
    proto_tree *hl7_tree = NULL;
    proto_item *ti = NULL;
    struct msh msh;
    int ret = 0;

    col_set_str(pinfo->cinfo, COL_PROTOCOL, "HL7");
    col_clear(pinfo->cinfo, COL_INFO);

    ret = parse_msh(tvb, pinfo, tree, offset + 1, &msh);

    if (ret == -1)
        return;

    /* enrich info column */
    if (event_present(&msh)) {
        if (offset == 0) {
            col_append_fstr(pinfo->cinfo, COL_INFO, "%s (%s)",
                            msh.message_type,
                            msh.trigger_event);
        } else {
            col_append_fstr(pinfo->cinfo, COL_INFO, ", %s (%s)",
                            msh.message_type,
                            msh.trigger_event);
        }
    } else {
        if (offset == 0) {
            col_append_str(pinfo->cinfo, COL_INFO,
                            msh.message_type);
        } else {
            col_append_fstr(pinfo->cinfo, COL_INFO, ", %s",
                            msh.message_type);
        }
    }
    /* set a fence so that subsequent col_clear calls will
     * not wipe out col information regarding this PDU */
    col_set_fence(pinfo->cinfo, COL_INFO);

    ti = proto_tree_add_item(tree, proto_hl7, tvb, offset, len, ENC_NA);
    if (event_present(&msh)) {
        proto_item_append_text(ti, ", Type: %s, Event: %s",
                               str_to_str(msh.message_type,
                                          hl7_msg_type_vals, "Unknown"),
                               str_to_str(msh.trigger_event,
                                          hl7_event_type_vals, "Unknown"));
    } else {
        proto_item_append_text(ti, ", Type: %s",
                               str_to_str(msh.message_type,
                                          hl7_msg_type_vals, "Unknown"));
    }
    hl7_tree = proto_item_add_subtree(ti, ett_hl7);
    /* SOB */
    if (global_hl7_llp) {
        proto_tree_add_item(hl7_tree, hf_hl7_llp_sob, tvb, sob_offset, 1, ENC_NA);
    }
    offset++;
    if (global_hl7_raw) {
        proto_tree_add_item(hl7_tree, hf_hl7_raw, tvb, offset, len - 3,
                            ENC_ASCII|ENC_NA);
    }

    /* body */
    while (offset < eob_offset) {
        gint next_offset = -1;
        gint segment_len = -1;
        gint segment_len_crlf = -1;
        /* FF: even if HL7 2.3.1 says each segment must be terminated with CR
         * we look either for a CR or an LF or both (I did find a system out
         * there that uses both) */
        segment_len = tvb_find_line_end(tvb, offset, -1, &next_offset, TRUE);
        if (segment_len == -1) {
            expert_add_info_format(pinfo, NULL, &ei_hl7_malformed,
                                   "Segments must be terminated with CR");
            return;
        }
        segment_len_crlf = next_offset - offset;
        dissect_hl7_segment(tvb, pinfo, hl7_tree,
                            offset, segment_len, segment_len_crlf, &msh);
        offset += segment_len_crlf;
    }
    /* EOB */
    if (global_hl7_llp) {
        proto_tree_add_item(hl7_tree, hf_hl7_llp_eob, tvb, eob_offset, 2,
                            ENC_BIG_ENDIAN);
    }
}

static int
dissect_hl7(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
{
    guint offset = 0;

    while (offset < tvb_reported_length(tvb)) {
        gint available = tvb_reported_length_remaining(tvb, offset);
        gint llp_eob_offset = tvb_find_guint16(tvb, offset, offset + available, LLP_EOB);

        if (llp_eob_offset == -1) {
            /* we ran out of data: ask for more */
            pinfo->desegment_offset = offset;
            pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT;
            return (offset + available);
        }

        /* tvb_find_ utilities return the *start* of the signature, here we
         * take care of the LLP_EOB bytes */
        gint llp_block_len = llp_eob_offset - offset + 2;

        /* FF: nasty case, check whether the capture started after the SOB
         * transmission. If this is the case we display these trailing bytes
         * as 'Data' and we will dissect the next complete message.
         */
        if (tvb_get_guint8(tvb, 0) != LLP_SOB) {
            tvbuff_t *new_tvb = tvb_new_subset_remaining(tvb, offset);
            call_data_dissector(new_tvb, pinfo, tree);
            return (offset + available);
        }

        /* FF: ok we got a complete LLP block '0x0B HL7-message 0x1C 0x0D',
         * do the dissection */
        dissect_hl7_message(tvb, offset, llp_block_len, pinfo, tree, data);
        offset += (guint)llp_block_len;
    }

    /* if we get here, then the end of the tvb matched with the end of a
       HL7 message. Happy days. */
    return tvb_captured_length(tvb);
}

static gboolean
dissect_hl7_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree _U_, void *data _U_)
{
    conversation_t *conversation = NULL;

    /* heuristic is based on first 5 bytes analisys, we assume
       0x0B + "MSH|" is good enough */
    if ((tvb_reported_length_remaining(tvb, 0) < 5) ||
        (tvb_get_guint8(tvb, 0) != LLP_SOB) ||
        (tvb_strncaseeql(tvb, 1, "MSH|", 4) != 0)) {
        return FALSE;
    }

    /* heuristic test passed, associate the non-heuristic port based
     * dissector function above with this flow for further processing,
     * the conversation framework will do the rest */
    conversation = find_or_create_conversation(pinfo);
    conversation_set_dissector(conversation, hl7_handle);

    /* Note Well!
     * If this PDU is complete everything is fine, the engine will call
     * dissect_hl7() providing the same data we have in this tvb.
     * If the PDU is *not* complete - i.e. we have only the first
     * fragment in this tvb - then dissect_hl7() will get only the
     * next bytes, hence the first PDU will not be properly displayed.
     * To fix this case we need to tell the dissector engine that we
     * need more data (desegment_len = MORE) and that we want
     * to continue the next processing from the beginning of the PDU
     * (desegment_offset = 0) because we did not consume/dissect
     * anything in this cycle. */
    gint llp_eob_offset = tvb_find_guint16(tvb, 0, -1, LLP_EOB);

    if (llp_eob_offset == -1) {
        pinfo->desegment_offset = 0;
        pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT;
    }

    return TRUE;
}

void
proto_reg_handoff_hl7(void)
{
    /* register as heuristic dissector for TCP */
    hl7_heur_handle = create_dissector_handle(dissect_hl7_heur, proto_hl7);
    heur_dissector_add("tcp", dissect_hl7_heur, "HL7 over TCP",
                       "hl7_tcp", proto_hl7, HEURISTIC_ENABLE);

    /* register as normal dissector for TCP well-known port */
    hl7_handle = create_dissector_handle(dissect_hl7, proto_hl7);
    dissector_add_uint_with_preference("tcp.port", TCP_PORT_HL7, hl7_handle);
}

void
proto_register_hl7(void)
{
    static hf_register_info hl7f_info[] = {
        { &hf_hl7_raw,
          { "raw message", "hl7.raw", FT_STRING,
            BASE_NONE, NULL, 0x0, NULL, HFILL }
        },
        { &hf_hl7_llp_sob,
          { "LLP Start Of Block", "hl7.llp.sob", FT_UINT8,
            BASE_HEX, NULL, 0x0, NULL, HFILL }
        },
        { &hf_hl7_llp_eob,
          { "LLP End Of Block", "hl7.llp.eob", FT_UINT16,
            BASE_HEX, NULL, 0x0, NULL, HFILL }
        },
        { &hf_hl7_raw_segment,
          { "raw segment", "hl7.raw.segment", FT_STRING,
            BASE_NONE, NULL, 0x0, NULL, HFILL }
        },
        { &hf_hl7_segment,
          { "xyz", "hl7.segment", FT_STRING,
            BASE_NONE, NULL, 0x0,
            NULL, HFILL }
        },
        { &hf_hl7_message_type,
          { "xyz", "hl7.message.type", FT_STRING,
            BASE_NONE, NULL, 0x0,
            NULL, HFILL }
        },
        { &hf_hl7_event_type,
          { "xyz", "hl7.event.type", FT_STRING,
            BASE_NONE, NULL, 0x0,
            NULL, HFILL }
        },
        { &hf_hl7_field,
          { "xyz", "hl7.field", FT_STRING,
            BASE_NONE, NULL, 0x0,
            NULL, HFILL }
        },
    };

    static gint *ett[] = {
        &ett_hl7,
        &ett_hl7_segment,
    };

    static ei_register_info ei[] = {
        { &ei_hl7_malformed, { "hl7.malformed", PI_MALFORMED, PI_WARN, "Malformed", EXPFILL }},
    };

    expert_module_t *expert_hl7 = NULL;
    module_t *hl7_module = NULL;

    proto_hl7 = proto_register_protocol("Health Level Seven", "HL7", "hl7");
    proto_register_field_array(proto_hl7, hl7f_info, array_length(hl7f_info));
    proto_register_subtree_array(ett, array_length(ett));
    expert_hl7 = expert_register_protocol(proto_hl7);
    expert_register_field_array(expert_hl7, ei, array_length(ei));
    hl7_module = prefs_register_protocol(proto_hl7, proto_reg_handoff_hl7);
    prefs_register_bool_preference(hl7_module, "display_raw",
                                   "Display raw text for HL7 message",
                                   "Specifies that the raw text of the "
                                   "HL7 message should be displayed "
                                   "in addition to the dissection tree",
                                   &global_hl7_raw);
    prefs_register_bool_preference(hl7_module, "display_llp",
                                   "Display LLP markers (Start/End Of Block)",
                                   "Specifies that the LLP session information "
                                   "should be displayed (Start/End Of Block) "
                                   "in addition to the dissection tree",
                                   &global_hl7_llp);
}

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