diff options
Diffstat (limited to 'epan/dissectors/packet-cipsafety.c')
-rw-r--r-- | epan/dissectors/packet-cipsafety.c | 3118 |
1 files changed, 3118 insertions, 0 deletions
diff --git a/epan/dissectors/packet-cipsafety.c b/epan/dissectors/packet-cipsafety.c new file mode 100644 index 00000000..76cf6f47 --- /dev/null +++ b/epan/dissectors/packet-cipsafety.c @@ -0,0 +1,3118 @@ +/* packet-cipsafety.c + * Routines for CIP (Common Industrial Protocol) Safety dissection + * CIP Safety Home: www.odva.org + * + * This dissector includes items from: + * CIP Volume 1: Common Industrial Protocol, Edition 3.24 + * CIP Volume 5: CIP Safety, Edition 2.22 + * + * Copyright 2011 + * Michael Mann <mmann@pyramidsolutions.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include <stdio.h> +#include <epan/packet.h> +#include <epan/expert.h> +#include <epan/proto_data.h> + +#include <wsutil/pint.h> +#include <wsutil/crc8.h> +#include <wsutil/crc16.h> +#include <wsutil/crc32.h> +#include "packet-cip.h" +#include "packet-cipsafety.h" + + +void proto_register_cipsafety(void); +void proto_reg_handoff_cipsafety(void); +/* The entry point to the actual dissection is: dissect_cipsafety */ + +/* Protocol handle for CIP Safety */ +static int proto_cipsafety = -1; +static int proto_cipsafety_base_data = -1; +static int proto_cipsafety_extended_data = -1; +static int proto_cipsafety_base_time_coord = -1; +static int proto_cipsafety_extended_time_coord = -1; +static int proto_cip_class_s_supervisor = -1; +static int proto_cip_class_s_validator = -1; +static int proto_cip = -1; + +static dissector_table_t subdissector_class_table; +static dissector_handle_t cip_class_s_supervisor_handle; +static dissector_handle_t cip_class_s_validator_handle; + +/* CIP Safety field identifiers */ +static int hf_cipsafety_data = -1; +static int hf_cipsafety_mode_byte = -1; +static int hf_cipsafety_mode_byte_run_idle = -1; +static int hf_cipsafety_mode_byte_not_run_idle = -1; +static int hf_cipsafety_mode_byte_tbd_2_bit = -1; +static int hf_cipsafety_mode_byte_tbd_2_copy = -1; +static int hf_cipsafety_mode_byte_ping_count = -1; +static int hf_cipsafety_mode_byte_tbd = -1; +static int hf_cipsafety_mode_byte_not_tbd = -1; +static int hf_cipsafety_crc_s1 = -1; +static int hf_cipsafety_crc_s1_status = -1; +static int hf_cipsafety_crc_s2 = -1; +static int hf_cipsafety_crc_s2_status = -1; +static int hf_cipsafety_crc_s3 = -1; +static int hf_cipsafety_crc_s3_status = -1; +static int hf_cipsafety_complement_crc_s3 = -1; +static int hf_cipsafety_complement_crc_s3_status = -1; +static int hf_cipsafety_timestamp = -1; +static int hf_cipsafety_ack_byte = -1; +static int hf_cipsafety_ack_byte_ping_count_reply = -1; +static int hf_cipsafety_ack_byte_reserved1 = -1; +static int hf_cipsafety_ack_byte_ping_response = -1; +static int hf_cipsafety_ack_byte_reserved2 = -1; +static int hf_cipsafety_ack_byte_parity_even = -1; +static int hf_cipsafety_ack_byte2 = -1; +static int hf_cipsafety_consumer_time_value = -1; +static int hf_cipsafety_mcast_byte = -1; +static int hf_cipsafety_mcast_byte_consumer_num = -1; +static int hf_cipsafety_mcast_byte_reserved1 = -1; +static int hf_cipsafety_mcast_byte_mai = -1; +static int hf_cipsafety_mcast_byte_reserved2 = -1; +static int hf_cipsafety_mcast_byte_parity_even = -1; +static int hf_cipsafety_mcast_byte2 = -1; +static int hf_cipsafety_time_correction = -1; +static int hf_cipsafety_crc_s5_0 = -1; +static int hf_cipsafety_crc_s5_1 = -1; +static int hf_cipsafety_crc_s5_2 = -1; +static int hf_cipsafety_crc_s5_status = -1; +static int hf_cipsafety_complement_data = -1; + +/* CIP Safety header field identifiers */ +static int hf_cip_reqrsp = -1; +static int hf_cip_data = -1; + +/* Safety Supervisor header field identifiers */ +static int hf_cip_ssupervisor_sc = -1; +static int hf_cip_ssupervisor_recover_data = -1; +static int hf_cip_ssupervisor_perform_diag_data = -1; +static int hf_cip_ssupervisor_configure_request_password = -1; +static int hf_cip_ssupervisor_configure_request_tunid = -1; +static int hf_cip_ssupervisor_configure_request_tunid_snn_timestamp = -1; +static int hf_cip_ssupervisor_configure_request_tunid_snn_date = -1; +static int hf_cip_ssupervisor_configure_request_tunid_snn_time = -1; +static int hf_cip_ssupervisor_configure_request_tunid_nodeid = -1; +static int hf_cip_ssupervisor_configure_request_ounid = -1; +static int hf_cip_ssupervisor_configure_request_ounid_snn_timestamp = -1; +static int hf_cip_ssupervisor_configure_request_ounid_snn_date = -1; +static int hf_cip_ssupervisor_configure_request_ounid_snn_time = -1; +static int hf_cip_ssupervisor_configure_request_ounid_nodeid = -1; +static int hf_cip_ssupervisor_validate_configuration_sccrc = -1; +static int hf_cip_ssupervisor_validate_configuration_scts_timestamp = -1; +static int hf_cip_ssupervisor_validate_configuration_scts_date = -1; +static int hf_cip_ssupervisor_validate_configuration_scts_time = -1; +static int hf_cip_ssupervisor_validate_configuration_ext_error = -1; +static int hf_cip_ssupervisor_set_password_current_password = -1; +static int hf_cip_ssupervisor_set_password_new_password = -1; +static int hf_cip_ssupervisor_configure_lock_value = -1; +static int hf_cip_ssupervisor_configure_lock_password = -1; +static int hf_cip_ssupervisor_configure_lock_tunid = -1; +static int hf_cip_ssupervisor_configure_lock_tunid_snn_timestamp = -1; +static int hf_cip_ssupervisor_configure_lock_tunid_snn_date = -1; +static int hf_cip_ssupervisor_configure_lock_tunid_snn_time = -1; +static int hf_cip_ssupervisor_configure_lock_tunid_nodeid = -1; +static int hf_cip_ssupervisor_mode_change_value = -1; +static int hf_cip_ssupervisor_mode_change_password = -1; +static int hf_cip_ssupervisor_reset_type = -1; +static int hf_cip_ssupervisor_reset_password = -1; +static int hf_cip_ssupervisor_reset_tunid = -1; +static int hf_cip_ssupervisor_reset_tunid_tunid_snn_timestamp = -1; +static int hf_cip_ssupervisor_reset_tunid_tunid_snn_date = -1; +static int hf_cip_ssupervisor_reset_tunid_tunid_snn_time = -1; +static int hf_cip_ssupervisor_reset_tunid_nodeid = -1; +static int hf_cip_ssupervisor_reset_attr_bitmap = -1; +static int hf_cip_ssupervisor_reset_attr_bitmap_macid = -1; +static int hf_cip_ssupervisor_reset_attr_bitmap_baudrate = -1; +static int hf_cip_ssupervisor_reset_attr_bitmap_tunid = -1; +static int hf_cip_ssupervisor_reset_attr_bitmap_password = -1; +static int hf_cip_ssupervisor_reset_attr_bitmap_cfunid = -1; +static int hf_cip_ssupervisor_reset_attr_bitmap_ocpunid = -1; +static int hf_cip_ssupervisor_reset_attr_bitmap_reserved = -1; +static int hf_cip_ssupervisor_reset_attr_bitmap_extended = -1; +static int hf_cip_ssupervisor_reset_password_data_size = -1; +static int hf_cip_ssupervisor_reset_password_data = -1; +static int hf_cip_ssupervisor_propose_tunid_tunid = -1; +static int hf_cip_ssupervisor_propose_tunid_tunid_snn_timestamp = -1; +static int hf_cip_ssupervisor_propose_tunid_tunid_snn_date = -1; +static int hf_cip_ssupervisor_propose_tunid_tunid_snn_time = -1; +static int hf_cip_ssupervisor_propose_tunid_tunid_nodeid = -1; +static int hf_cip_ssupervisor_apply_tunid_tunid = -1; +static int hf_cip_ssupervisor_apply_tunid_tunid_snn_timestamp = -1; +static int hf_cip_ssupervisor_apply_tunid_tunid_snn_date = -1; +static int hf_cip_ssupervisor_apply_tunid_tunid_snn_time = -1; +static int hf_cip_ssupervisor_apply_tunid_tunid_nodeid = -1; + +static int hf_cip_ssupervisor_class_subclass = -1; +static int hf_cip_ssupervisor_num_attr = -1; +static int hf_cip_ssupervisor_attr_list = -1; +static int hf_cip_ssupervisor_manufacture_name = -1; +static int hf_cip_ssupervisor_manufacture_model_number = -1; +static int hf_cip_ssupervisor_sw_rev_level = -1; +static int hf_cip_ssupervisor_hw_rev_level = -1; +static int hf_cip_ssupervisor_manufacture_serial_number = -1; +static int hf_cip_ssupervisor_device_config = -1; +static int hf_cip_ssupervisor_device_status = -1; +static int hf_cip_ssupervisor_exception_status = -1; +static int hf_cip_ssupervisor_exception_detail_ced_size = -1; +static int hf_cip_ssupervisor_exception_detail_ced_detail = -1; +static int hf_cip_ssupervisor_exception_detail_ded_size = -1; +static int hf_cip_ssupervisor_exception_detail_ded_detail = -1; +static int hf_cip_ssupervisor_exception_detail_med_size = -1; +static int hf_cip_ssupervisor_exception_detail_med_detail = -1; +static int hf_cip_ssupervisor_alarm_enable = -1; +static int hf_cip_ssupervisor_warning_enable = -1; +static int hf_cip_ssupervisor_time = -1; +static int hf_cip_ssupervisor_clock_power_cycle_behavior = -1; +static int hf_cip_ssupervisor_last_maintenance_date = -1; +static int hf_cip_ssupervisor_next_scheduled_maintenance_date = -1; +static int hf_cip_ssupervisor_scheduled_maintenance_expiration_timer = -1; +static int hf_cip_ssupervisor_scheduled_maintenance_expiration_warning_enable = -1; +static int hf_cip_ssupervisor_run_hours = -1; +static int hf_cip_ssupervisor_configuration_lock = -1; +static int hf_cip_ssupervisor_configuration_unid_snn_timestamp = -1; +static int hf_cip_ssupervisor_configuration_unid_snn_date = -1; +static int hf_cip_ssupervisor_configuration_unid_snn_time = -1; +static int hf_cip_ssupervisor_configuration_unid_nodeid = -1; +static int hf_cip_ssupervisor_safety_configuration_id_snn_timestamp = -1; +static int hf_cip_ssupervisor_safety_configuration_id_snn_date = -1; +static int hf_cip_ssupervisor_safety_configuration_id_snn_time = -1; +static int hf_cip_ssupervisor_safety_configuration_id_sccrc = -1; +static int hf_cip_ssupervisor_target_unid_snn_timestamp = -1; +static int hf_cip_ssupervisor_target_unid_snn_date = -1; +static int hf_cip_ssupervisor_target_unid_snn_time = -1; +static int hf_cip_ssupervisor_target_unid_nodeid = -1; +static int hf_cip_ssupervisor_cp_owners_num_entries = -1; +static int hf_cip_ssupervisor_output_cp_owners_ocpunid_snn_timestamp = -1; +static int hf_cip_ssupervisor_output_cp_owners_ocpunid_snn_date = -1; +static int hf_cip_ssupervisor_output_cp_owners_ocpunid_snn_time = -1; +static int hf_cip_ssupervisor_output_cp_owners_ocpunid_nodeid = -1; +static int hf_cip_ssupervisor_cp_owners_app_path_size = -1; +static int hf_cip_ssupervisor_proposed_tunid_snn_timestamp = -1; +static int hf_cip_ssupervisor_proposed_tunid_snn_date = -1; +static int hf_cip_ssupervisor_proposed_tunid_snn_time = -1; +static int hf_cip_ssupervisor_proposed_tunid_nodeid = -1; +static int hf_cip_ssupervisor_instance_subclass = -1; + + +/* Safety Validator header field identifiers */ +static int hf_cip_svalidator_sc = -1; + +static int hf_cip_svalidator_sconn_fault_count = -1; +static int hf_cip_svalidator_state = -1; +static int hf_cip_svalidator_type = -1; +static int hf_cip_svalidator_type_pc = -1; +static int hf_cip_svalidator_type_conn_type = -1; +static int hf_cip_svalidator_ping_epi = -1; +static int hf_cip_svalidator_time_coord_msg_min_mult_size = -1; +static int hf_cip_svalidator_time_coord_msg_min_mult_item = -1; +static int hf_cip_svalidator_network_time_multiplier_size = -1; +static int hf_cip_svalidator_network_time_multiplier_item = -1; +static int hf_cip_svalidator_timeout_multiplier_size = -1; +static int hf_cip_svalidator_timeout_multiplier_item = -1; +static int hf_cip_svalidator_max_consumer_num = -1; +static int hf_cip_svalidator_data_conn_inst = -1; +static int hf_cip_svalidator_coordination_conn_inst_size = -1; +static int hf_cip_svalidator_coordination_conn_inst_item = -1; +static int hf_cip_svalidator_correction_conn_inst = -1; +static int hf_cip_svalidator_cco_binding = -1; +static int hf_cip_svalidator_max_data_age = -1; +static int hf_cip_svalidator_error_code = -1; +static int hf_cip_svalidator_prod_cons_fault_count_size = -1; +static int hf_cip_svalidator_prod_cons_fault_count_item = -1; + +static int hf_cip_sercosiii_link_snn = -1; +static int hf_cip_sercosiii_link_communication_cycle_time = -1; +static int hf_cip_sercosiii_link_interface_status = -1; +static int hf_cip_sercosiii_link_error_count_mstps = -1; +static int hf_cip_sercosiii_link_sercos_address = -1; +static int hf_cip_sercosiii_link_error_count_p1 = -1; +static int hf_cip_sercosiii_link_error_count_p2 = -1; + +/* Initialize the subtree pointers */ +static gint ett_cip_safety = -1; +static gint ett_path = -1; +static gint ett_cipsafety_mode_byte = -1; +static gint ett_cipsafety_ack_byte = -1; +static gint ett_cipsafety_mcast_byte = -1; + +static gint ett_cip_class_s_supervisor = -1; +static gint ett_ssupervisor_rrsc = -1; +static gint ett_ssupervisor_cmd_data = -1; +static gint ett_ssupervisor_propose_tunid = -1; +static gint ett_ssupervisor_propose_tunid_snn = -1; +static gint ett_ssupervisor_configure_request_tunid = -1; +static gint ett_ssupervisor_configure_request_tunid_snn = -1; +static gint ett_ssupervisor_configure_request_ounid = -1; +static gint ett_ssupervisor_configure_request_ounid_snn = -1; +static gint ett_ssupervisor_configure_lock_tunid = -1; +static gint ett_ssupervisor_configure_lock_tunid_snn = -1; +static gint ett_ssupervisor_reset_tunid = -1; +static gint ett_ssupervisor_reset_tunid_snn = -1; +static gint ett_ssupervisor_apply_tunid = -1; +static gint ett_ssupervisor_apply_tunid_snn = -1; +static gint ett_exception_detail_common = -1; +static gint ett_exception_detail_device = -1; +static gint ett_exception_detail_manufacturer = -1; +static gint ett_ssupervisor_configuration_unid = -1; +static gint ett_ssupervisor_configuration_unid_snn = -1; +static gint ett_ssupervisor_target_unid = -1; +static gint ett_ssupervisor_target_unid_snn = -1; +static gint ett_ssupervisor_output_cp_owners = -1; +static gint ett_ssupervisor_output_cp_owners_ocpunid = -1; +static gint ett_ssupervisor_output_cp_owners_ocpunid_snn = -1; +static gint ett_ssupervisor_proposed_tunid = -1; +static gint ett_ssupervisor_proposed_tunid_snn = -1; +static gint ett_cip_ssupervisor_reset_attr_bitmap = -1; + +static gint ett_cip_class_s_validator = -1; +static gint ett_svalidator_rrsc = -1; +static gint ett_svalidator_cmd_data = -1; +static gint ett_svalidator_type = -1; + +static expert_field ei_cipsafety_tbd_not_complemented = EI_INIT; +static expert_field ei_cipsafety_tbd2_not_copied = EI_INIT; +static expert_field ei_cipsafety_run_idle_not_complemented = EI_INIT; +static expert_field ei_mal_io = EI_INIT; +static expert_field ei_mal_sercosiii_link_error_count_p1p2 = EI_INIT; +static expert_field ei_cipsafety_not_complement_data = EI_INIT; +static expert_field ei_cipsafety_crc_s1 = EI_INIT; +static expert_field ei_cipsafety_crc_s2 = EI_INIT; +static expert_field ei_cipsafety_crc_s3 = EI_INIT; +static expert_field ei_cipsafety_complement_crc_s3 = EI_INIT; +static expert_field ei_cipsafety_crc_s5 = EI_INIT; + +static expert_field ei_mal_ssupervisor_exception_detail_ced = EI_INIT; +static expert_field ei_mal_ssupervisor_exception_detail_ded = EI_INIT; +static expert_field ei_mal_ssupervisor_exception_detail_med = EI_INIT; +static expert_field ei_mal_ssupervisor_configuration_unid = EI_INIT; +static expert_field ei_mal_ssupervisor_safety_configuration_id = EI_INIT; +static expert_field ei_mal_ssupervisor_target_unid = EI_INIT; +static expert_field ei_mal_ssupervisor_cp_owners = EI_INIT; +static expert_field ei_mal_ssupervisor_cp_owners_entry = EI_INIT; +static expert_field ei_mal_ssupervisor_cp_owners_app_path_size = EI_INIT; +static expert_field ei_mal_ssupervisor_proposed_tunid = EI_INIT; +static expert_field ei_info_ssupervisor_tunid_cancel = EI_INIT; + +static expert_field ei_mal_svalidator_type = EI_INIT; +static expert_field ei_mal_svalidator_time_coord_msg_min_mult = EI_INIT; +static expert_field ei_mal_svalidator_network_time_multiplier = EI_INIT; +static expert_field ei_mal_svalidator_timeout_multiplier = EI_INIT; +static expert_field ei_mal_svalidator_coordination_conn_inst = EI_INIT; +static expert_field ei_mal_svalidator_prod_cons_fault_count = EI_INIT; + +static dissector_handle_t cipsafety_handle; +static dissector_handle_t cipsafety_base_data_handle; +static dissector_handle_t cipsafety_extended_data_handle; +static dissector_handle_t cipsafety_base_time_coord_handle; +static dissector_handle_t cipsafety_extended_time_coord_handle; + +typedef struct cip_safety_packet_data { + guint16 rollover_value; +} cip_safety_packet_data_t; + +#define MODE_BYTE_CRC_S1_MASK 0xE0 +#define MODE_BYTE_CRC_S1_TIME_STAMP_MASK 0x1F +#define MODE_BYTE_CRC_S3_MASK 0xE0 +#define MODE_BYTE_CRC_S5_BASE_MASK 0xE0 +#define MODE_BYTE_CRC_S5_EXTENDED_MASK 0x1F + +const value_string cipsafety_snn_date_vals[8] = { + + { 0, "NULL SNN" }, + { 1, "Manual Setting - Backplane" }, + { 2, "Manual Setting - ControlNet" }, + { 4, "Manual Setting - EtherNet/IP" }, + { 5, "Manual Setting - DeviceNet" }, + { 6, "Manual Setting - SERCOS III" }, + { 65535, "No SNN Set" }, + + { 0, NULL } +}; + +static const true_false_string cip_safety_vals_active_idle = { + "Active", + "Idle", +}; + +/* Translate function to string - CIP Service codes for Safety Supervisor */ +static const value_string cip_sc_vals_ssupervisor[] = { + GENERIC_SC_LIST + + /* Some class specific services */ + { SC_SSUPER_RECOVER, "Recover" }, + { SC_SSUPER_PERFORM_DIAGNOSTICS, "Perform Diagnostics" }, + { SC_SSUPER_CONFIGURE_REQUEST, "Configure Request" }, + { SC_SSUPER_VALIDATE_CONFIGURATION, "Validate Configuration" }, + { SC_SSUPER_SET_PASSWORD, "Set Password" }, + { SC_SSUPER_CONFIGURATION_LOCK, "Configuration (Un)Lock" }, + { SC_SSUPER_MODE_CHANGE, "Mode Change" }, + { SC_SSUPER_SAFETY_RESET, "Safety Reset" }, + { SC_SSUPER_RESET_PASSWORD, "Reset Password" }, + { SC_SSUPER_PROPOSE_TUNID, "Propose TUNID" }, + { SC_SSUPER_APPLY_TUNID, "Apply TUNID" }, + { SC_SSUPER_PROPOSE_TUNID_LIST, "Propose TUNID List" }, + { SC_SSUPER_APPLY_TUNID_LIST, "Apply TUNID List" }, + + { 0, NULL } +}; + +#define SC_SVALID_RESET_ERROR 0x4B + +/* Translate function to string - CIP Service codes for Safety Validator */ +static const value_string cip_sc_vals_svalidator[] = { + GENERIC_SC_LIST + + /* Some class specific services */ + { SC_SVALID_RESET_ERROR, "Reset Error" }, + + { 0, NULL } +}; + +static const value_string cip_ssupervisor_validate_configuration_ext_error_vals[] = { + { 1, "CRC mismatch" }, + { 2, "Invalid Configuration Parameter" }, + { 3, "TUNID Not Set" }, + + { 0, NULL } +}; + +static const value_string cip_ssupervisor_lock_vals[] = { + { 0, "Unlocked" }, + { 1, "Locked" }, + + { 0, NULL } +}; + +static const value_string cip_ssupervisor_change_mode_vals[] = { + { 0, "Idle" }, + { 1, "Executing" }, + + { 0, NULL } +}; + +static const value_string cip_ssupervisor_device_status_type_vals[] = { + { 0, "Undefined" }, + { 1, "Self-Testing" }, + { 2, "Idle" }, + { 3, "Self-Test Exception" }, + { 4, "Executing" }, + { 5, "Abort" }, + { 6, "Critical Fault" }, + { 7, "Configuring" }, + { 8, "Waiting for TUNID" }, + { 51, "Waiting for TUNID with Torque Permitted" }, + { 52, "Executing with Torque Permitted" }, + + { 0, NULL } +}; + +static const value_string cip_ssupervisor_clock_power_cycle_type_vals[] = { + { 0, "Clock always resets" }, + { 1, "Clock in NVS at power down" }, + { 2, "Clock is battery-backed" }, + + { 0, NULL } +}; + +static const value_string cip_svalidator_state_vals[] = { + { 0, "Unallocated" }, + { 1, "Initializing" }, + { 2, "Established" }, + { 3, "Connection failed" }, + + { 0, NULL } +}; + +static const value_string cip_svalidator_type_pc_vals[] = { + { 0, "Producer" }, + { 1, "Consumer" }, + + { 0, NULL } +}; + +static const value_string cip_svalidator_type_conn_type_vals[] = { + { 0, "Unallocated" }, + { 1, "Single-cast" }, + { 2, "Multi-cast" }, + + { 0, NULL } +}; + +const range_string safety_max_consumer_numbers[] = { + { 1, 1, "Single-cast" }, + { 2, 15, "Multicast" }, + + { 0, 0, NULL } +}; + +void cip_safety_128us_fmt(gchar *s, guint32 value) +{ + // Each tick is 128us. + snprintf(s, ITEM_LABEL_LENGTH, "%d (%.3fms)", value, value * 0.128); +} + +void +dissect_unid(tvbuff_t *tvb, packet_info *pinfo, int offset, proto_item *pi, + const char* snn_name, int hf_snn_timestamp, + int hf_snn_date, int hf_snn_time, int hf_nodeid, gint ett, gint ett_snn) +{ + proto_tree *tree, *snn_tree; + + tree = proto_item_add_subtree(pi, ett); + + snn_tree = proto_tree_add_subtree(tree, tvb, offset, 6, ett_snn, NULL, snn_name); + dissect_cipsafety_snn(snn_tree, tvb, pinfo, offset, hf_snn_timestamp, hf_snn_date, hf_snn_time); + + proto_tree_add_item(tree, hf_nodeid, tvb, offset+6, 4, ENC_LITTLE_ENDIAN); +} + +void dissect_cipsafety_snn(proto_tree *tree, tvbuff_t *tvb, packet_info *pinfo _U_, int offset, + int hf_real_datetime, int hf_date, int hf_time) +{ + guint16 date; + + date = tvb_get_letohs(tvb, offset+4); + + if ((date >= 11688) && (date <= 65534)) + { + /* value is an actual timestamp */ + dissect_cip_date_and_time(tree, tvb, offset, hf_real_datetime); + } + else + { + /* Treated as UINT16 and UINT32 values */ + proto_tree_add_item(tree, hf_time, tvb, offset, 4, ENC_LITTLE_ENDIAN); + proto_tree_add_item(tree, hf_date, tvb, offset + 4, 2, ENC_LITTLE_ENDIAN); + } +} + +static void dissect_safety_supervisor_safety_reset(proto_tree* cmd_data_tree, tvbuff_t* tvb, int offset, packet_info* pinfo) +{ + guint32 reset_type; + proto_tree_add_item_ret_uint(cmd_data_tree, hf_cip_ssupervisor_reset_type, tvb, offset, 1, ENC_LITTLE_ENDIAN, &reset_type); + + proto_tree_add_item(cmd_data_tree, hf_cip_ssupervisor_reset_password, tvb, offset + 1, 16, ENC_NA); + proto_item* pi = proto_tree_add_item(cmd_data_tree, hf_cip_ssupervisor_reset_tunid, tvb, offset + 17, 10, ENC_NA); + dissect_unid(tvb, pinfo, offset + 17, pi, "TUNID SNN", + hf_cip_ssupervisor_reset_tunid_tunid_snn_timestamp, + hf_cip_ssupervisor_reset_tunid_tunid_snn_date, + hf_cip_ssupervisor_reset_tunid_tunid_snn_time, + hf_cip_ssupervisor_reset_tunid_nodeid, + ett_ssupervisor_reset_tunid, + ett_ssupervisor_reset_tunid_snn); + + /* Attribute bitmap only included on Reset Type 2 */ + if (reset_type == 2) + { + pi = proto_tree_add_item(cmd_data_tree, hf_cip_ssupervisor_reset_attr_bitmap, tvb, offset + 27, 1, ENC_LITTLE_ENDIAN); + + proto_tree* bitmap_tree = proto_item_add_subtree(pi, ett_cip_ssupervisor_reset_attr_bitmap); + proto_tree_add_item(bitmap_tree, hf_cip_ssupervisor_reset_attr_bitmap_macid, tvb, offset + 27, 1, ENC_LITTLE_ENDIAN); + proto_tree_add_item(bitmap_tree, hf_cip_ssupervisor_reset_attr_bitmap_baudrate, tvb, offset + 27, 1, ENC_LITTLE_ENDIAN); + proto_tree_add_item(bitmap_tree, hf_cip_ssupervisor_reset_attr_bitmap_tunid, tvb, offset + 27, 1, ENC_LITTLE_ENDIAN); + proto_tree_add_item(bitmap_tree, hf_cip_ssupervisor_reset_attr_bitmap_password, tvb, offset + 27, 1, ENC_LITTLE_ENDIAN); + proto_tree_add_item(bitmap_tree, hf_cip_ssupervisor_reset_attr_bitmap_cfunid, tvb, offset + 27, 1, ENC_LITTLE_ENDIAN); + proto_tree_add_item(bitmap_tree, hf_cip_ssupervisor_reset_attr_bitmap_ocpunid, tvb, offset + 27, 1, ENC_LITTLE_ENDIAN); + proto_tree_add_item(bitmap_tree, hf_cip_ssupervisor_reset_attr_bitmap_reserved, tvb, offset + 27, 1, ENC_LITTLE_ENDIAN); + proto_tree_add_item(bitmap_tree, hf_cip_ssupervisor_reset_attr_bitmap_extended, tvb, offset + 27, 1, ENC_LITTLE_ENDIAN); + } +} + +static void detect_cancel_propose_apply_operation(tvbuff_t* tvb, int offset, packet_info* pinfo, proto_item* item) +{ + // Check for all FFs. + guint64 part1 = tvb_get_guint64(tvb, offset, ENC_LITTLE_ENDIAN); + guint16 part2 = tvb_get_guint16(tvb, offset + 8, ENC_LITTLE_ENDIAN); + if (part1 == 0xFFFFFFFFFFFFFFFF && part2 == 0xFFFF) + { + expert_add_info(pinfo, item, &ei_info_ssupervisor_tunid_cancel); + } +} + +/************************************************ + * + * Dissector for CIP Safety Supervisor Object + * + ************************************************/ +static void +dissect_cip_s_supervisor_data( proto_tree *item_tree, + tvbuff_t *tvb, int offset, int item_length, packet_info *pinfo ) +{ + proto_item *pi, *rrsc_item; + proto_tree *rrsc_tree, *cmd_data_tree; + int req_path_size; + int temp_data; + guint8 service, gen_status, add_stat_size; + cip_simple_request_info_t req_data; + + col_set_str(pinfo->cinfo, COL_PROTOCOL, "CIPS Supervisor"); + + /* Add Service code & Request/Response tree */ + service = tvb_get_guint8( tvb, offset ); + rrsc_tree = proto_tree_add_subtree( item_tree, tvb, offset, 1, ett_ssupervisor_rrsc, &rrsc_item, "Service: " ); + + /* Add Request/Response */ + proto_tree_add_item( rrsc_tree, hf_cip_reqrsp, tvb, offset, 1, ENC_LITTLE_ENDIAN ); + + proto_item_append_text( rrsc_item, "%s (%s)", + val_to_str( ( service & CIP_SC_MASK ), cip_sc_vals_ssupervisor , "Unknown Service (0x%02x)"), + val_to_str_const( ( service & CIP_SC_RESPONSE_MASK )>>7, cip_sc_rr, "") ); + + /* Add Service code */ + proto_tree_add_item(rrsc_tree, hf_cip_ssupervisor_sc, tvb, offset, 1, ENC_LITTLE_ENDIAN ); + + load_cip_request_data(pinfo, &req_data); + + if (service & CIP_SC_RESPONSE_MASK) + { + /* Response message */ + + /* Add additional status size */ + gen_status = tvb_get_guint8( tvb, offset+2 ); + add_stat_size = tvb_get_guint8( tvb, offset+3 ) * 2; + + /* If there is any command specific data create a sub-tree for it */ + if( ( item_length-4-add_stat_size ) != 0 ) + { + cmd_data_tree = proto_tree_add_subtree( item_tree, tvb, offset+4+add_stat_size, + item_length-4-add_stat_size, ett_ssupervisor_cmd_data, NULL, "Command Specific Data" ); + + if( gen_status == CI_GRC_SUCCESS ) + { + switch (service & CIP_SC_MASK) + { + case SC_SSUPER_VALIDATE_CONFIGURATION: + proto_tree_add_item(cmd_data_tree, hf_cip_ssupervisor_validate_configuration_sccrc, + tvb, offset+4+add_stat_size, 4, ENC_LITTLE_ENDIAN); + dissect_cipsafety_snn(cmd_data_tree, + tvb, pinfo, offset+4+add_stat_size+4, + hf_cip_ssupervisor_validate_configuration_scts_timestamp, + hf_cip_ssupervisor_validate_configuration_scts_date, + hf_cip_ssupervisor_validate_configuration_scts_time); + break; + } + } + else if ((gen_status == 0xD0) && ((service & CIP_SC_MASK) == SC_SSUPER_VALIDATE_CONFIGURATION)) + { + if (add_stat_size > 0) + { + proto_tree_add_item(cmd_data_tree, hf_cip_ssupervisor_validate_configuration_ext_error, + tvb, offset+4, 2, ENC_LITTLE_ENDIAN); + } + } + else + { + /* Error responses */ + + /* Add data */ + proto_tree_add_item(cmd_data_tree, hf_cip_data, + tvb, offset+4+add_stat_size, item_length-4-add_stat_size, ENC_NA); + } + } + + } /* End of if reply */ + else + { + /* Request message */ + + req_path_size = tvb_get_guint8( tvb, offset+1 )*2; + + /* If there is any command specific data create a sub-tree for it */ + if( (item_length-req_path_size-2) != 0 ) + { + cmd_data_tree = proto_tree_add_subtree( item_tree, tvb, offset+2+req_path_size, item_length-req_path_size-2, + ett_ssupervisor_cmd_data, NULL, "Command Specific Data" ); + + /* Check what service code that received */ + switch (service) + { + case SC_SSUPER_RECOVER: + proto_tree_add_item(cmd_data_tree, hf_cip_ssupervisor_recover_data, + tvb, offset+2+req_path_size, item_length-req_path_size-2, ENC_NA); + break; + case SC_SSUPER_PERFORM_DIAGNOSTICS: + proto_tree_add_item(cmd_data_tree, hf_cip_ssupervisor_perform_diag_data, + tvb, offset+2+req_path_size, item_length-req_path_size-2, ENC_NA); + break; + case SC_SSUPER_CONFIGURE_REQUEST: + proto_tree_add_item(cmd_data_tree, hf_cip_ssupervisor_configure_request_password, + tvb, offset+2+req_path_size, 16, ENC_NA); + pi = proto_tree_add_item(cmd_data_tree, hf_cip_ssupervisor_configure_request_tunid, + tvb, offset+2+req_path_size+16, 10, ENC_NA); + dissect_unid(tvb, pinfo, offset+2+req_path_size+16, pi, "TUNID SNN", + hf_cip_ssupervisor_configure_request_tunid_snn_timestamp, + hf_cip_ssupervisor_configure_request_tunid_snn_date, + hf_cip_ssupervisor_configure_request_tunid_snn_time, + hf_cip_ssupervisor_configure_request_tunid_nodeid, + ett_ssupervisor_configure_request_tunid, + ett_ssupervisor_configure_request_tunid_snn); + pi = proto_tree_add_item(cmd_data_tree, hf_cip_ssupervisor_configure_request_ounid, + tvb, offset+2+req_path_size+26, 10, ENC_NA); + dissect_unid(tvb, pinfo, offset+2+req_path_size+16, pi, "OUNID SNN", + hf_cip_ssupervisor_configure_request_ounid_snn_timestamp, + hf_cip_ssupervisor_configure_request_ounid_snn_date, + hf_cip_ssupervisor_configure_request_ounid_snn_time, + hf_cip_ssupervisor_configure_request_ounid_nodeid, + ett_ssupervisor_configure_request_ounid, + ett_ssupervisor_configure_request_ounid_snn); + break; + case SC_SSUPER_VALIDATE_CONFIGURATION: + proto_tree_add_item(cmd_data_tree, hf_cip_ssupervisor_validate_configuration_sccrc, + tvb, offset+2+req_path_size, 4, ENC_LITTLE_ENDIAN); + dissect_cipsafety_snn(cmd_data_tree, tvb, pinfo, offset+2+req_path_size+4, + hf_cip_ssupervisor_validate_configuration_scts_timestamp, + hf_cip_ssupervisor_validate_configuration_scts_date, + hf_cip_ssupervisor_validate_configuration_scts_time); + break; + case SC_SSUPER_SET_PASSWORD: + proto_tree_add_item(cmd_data_tree, hf_cip_ssupervisor_set_password_current_password, + tvb, offset+2+req_path_size, 16, ENC_NA); + proto_tree_add_item(cmd_data_tree, hf_cip_ssupervisor_set_password_new_password, + tvb, offset+2+req_path_size+16, 16, ENC_NA); + break; + case SC_SSUPER_CONFIGURATION_LOCK: + proto_tree_add_item(cmd_data_tree, hf_cip_ssupervisor_configure_lock_value, + tvb, offset+2+req_path_size, 1, ENC_LITTLE_ENDIAN); + proto_tree_add_item(cmd_data_tree, hf_cip_ssupervisor_configure_lock_password, + tvb, offset+2+req_path_size+1, 16, ENC_NA); + pi = proto_tree_add_item(cmd_data_tree, hf_cip_ssupervisor_configure_lock_tunid, + tvb, offset+2+req_path_size+17, 10, ENC_NA); + dissect_unid(tvb, pinfo, offset+2+req_path_size+17, pi, "TUNID SNN", + hf_cip_ssupervisor_configure_lock_tunid_snn_timestamp, + hf_cip_ssupervisor_configure_lock_tunid_snn_date, + hf_cip_ssupervisor_configure_lock_tunid_snn_time, + hf_cip_ssupervisor_configure_lock_tunid_nodeid, + ett_ssupervisor_configure_lock_tunid, + ett_ssupervisor_configure_lock_tunid_snn); + break; + case SC_SSUPER_MODE_CHANGE: + proto_tree_add_item(cmd_data_tree, hf_cip_ssupervisor_mode_change_value, + tvb, offset+2+req_path_size, 1, ENC_LITTLE_ENDIAN); + proto_tree_add_item(cmd_data_tree, hf_cip_ssupervisor_mode_change_password, + tvb, offset+2+req_path_size+1, 16, ENC_NA); + break; + case SC_SSUPER_SAFETY_RESET: + dissect_safety_supervisor_safety_reset(cmd_data_tree, tvb, offset + 2 + req_path_size, pinfo); + break; + case SC_SSUPER_RESET_PASSWORD: + proto_tree_add_item(cmd_data_tree, hf_cip_ssupervisor_reset_password_data_size, + tvb, offset+2+req_path_size, 1, ENC_LITTLE_ENDIAN); + temp_data = tvb_get_guint8(tvb, offset+2+req_path_size); + proto_tree_add_item(cmd_data_tree, hf_cip_ssupervisor_reset_password_data, + tvb, offset+2+req_path_size+1, temp_data, ENC_NA); + break; + case SC_SSUPER_PROPOSE_TUNID: + pi = proto_tree_add_item(cmd_data_tree, hf_cip_ssupervisor_propose_tunid_tunid, + tvb, offset+2+req_path_size, 10, ENC_NA); + dissect_unid(tvb, pinfo, offset+2+req_path_size, pi, "TUNID SNN", + hf_cip_ssupervisor_propose_tunid_tunid_snn_timestamp, + hf_cip_ssupervisor_propose_tunid_tunid_snn_date, + hf_cip_ssupervisor_propose_tunid_tunid_snn_time, + hf_cip_ssupervisor_propose_tunid_tunid_nodeid, + ett_ssupervisor_propose_tunid, + ett_ssupervisor_propose_tunid_snn); + + detect_cancel_propose_apply_operation(tvb, offset + 2 + req_path_size, pinfo, pi); + break; + case SC_SSUPER_APPLY_TUNID: + pi = proto_tree_add_item(cmd_data_tree, hf_cip_ssupervisor_apply_tunid_tunid, + tvb, offset+2+req_path_size, 10, ENC_NA); + dissect_unid(tvb, pinfo, offset+2+req_path_size, pi, "TUNID SNN", + hf_cip_ssupervisor_apply_tunid_tunid_snn_timestamp, + hf_cip_ssupervisor_apply_tunid_tunid_snn_date, + hf_cip_ssupervisor_apply_tunid_tunid_snn_time, + hf_cip_ssupervisor_apply_tunid_tunid_nodeid, + ett_ssupervisor_apply_tunid, + ett_ssupervisor_apply_tunid_snn); + + detect_cancel_propose_apply_operation(tvb, offset + 2 + req_path_size, pinfo, pi); + break; + default: + proto_tree_add_item(cmd_data_tree, hf_cip_data, + tvb, offset+2+req_path_size, item_length-req_path_size-2, ENC_NA); + } + + } /* End of if command-specific data present */ + + } /* End of if-else( request ) */ + + add_cip_service_to_info_column(pinfo, service, cip_sc_vals_ssupervisor); +} + +static int +dissect_cip_class_s_supervisor(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) +{ + proto_item *ti; + proto_tree *class_tree; + + /* Create display subtree for the protocol */ + ti = proto_tree_add_item(tree, proto_cip_class_s_supervisor, tvb, 0, -1, ENC_NA); + class_tree = proto_item_add_subtree( ti, ett_cip_class_s_supervisor ); + + dissect_cip_s_supervisor_data( class_tree, tvb, 0, tvb_reported_length(tvb), pinfo ); + + return tvb_reported_length(tvb); +} + +static int dissect_s_supervisor_exception_detail(proto_tree *tree, proto_item *item, tvbuff_t *tvb, int offset, int hf_size, int hf_data) +{ + guint32 size; + proto_tree_add_item_ret_uint(tree, hf_size, tvb, offset, 1, ENC_LITTLE_ENDIAN, &size); + + proto_tree_add_item(tree, hf_data, tvb, offset+1, size, ENC_NA ); + proto_item_set_len(item, size+1); + + return size+1; +} + +static int dissect_s_supervisor_exception_detail_common(packet_info *pinfo, proto_tree *tree, proto_item *item, tvbuff_t *tvb, + int offset, int total_len) +{ + proto_item *pi; + proto_tree *item_tree; + int total_size = 0, size; + + item_tree = proto_tree_add_subtree(tree, tvb, offset, 1, ett_exception_detail_common, &pi, "Common Exception Detail"); + size = dissect_s_supervisor_exception_detail(item_tree, pi, tvb, offset, + hf_cip_ssupervisor_exception_detail_ced_size, + hf_cip_ssupervisor_exception_detail_ced_detail); + if (size == 0) + { + expert_add_info(pinfo, item, &ei_mal_ssupervisor_exception_detail_ced); + return total_len; + } + total_size += size; + + item_tree = proto_tree_add_subtree(tree, tvb, offset + total_size, 1, ett_exception_detail_device, &pi, "Device Exception Detail"); + size = dissect_s_supervisor_exception_detail(item_tree, pi, tvb, offset + total_size, + hf_cip_ssupervisor_exception_detail_ded_size, + hf_cip_ssupervisor_exception_detail_ded_detail); + if (size == 0) + { + expert_add_info(pinfo, item, &ei_mal_ssupervisor_exception_detail_ded); + return total_len; + } + total_size += size; + + item_tree = proto_tree_add_subtree(tree, tvb, offset + total_size, 1, ett_exception_detail_manufacturer, &pi, "Manufacturer Exception Detail"); + size = dissect_s_supervisor_exception_detail(item_tree, pi, tvb, offset + total_size, + hf_cip_ssupervisor_exception_detail_med_size, + hf_cip_ssupervisor_exception_detail_med_detail); + if (size == 0) + { + expert_add_info(pinfo, item, &ei_mal_ssupervisor_exception_detail_med); + return total_len; + } + total_size += size; + + return total_size; +} + +static int dissect_s_supervisor_configuration_unid(packet_info *pinfo, proto_tree *tree _U_, proto_item *item, + tvbuff_t *tvb, int offset, int total_len) +{ + if (total_len < 10) + { + expert_add_info(pinfo, item, &ei_mal_ssupervisor_configuration_unid); + return total_len; + } + + dissect_unid(tvb, pinfo, offset, item, "CFUNID SNN", + hf_cip_ssupervisor_configuration_unid_snn_timestamp, + hf_cip_ssupervisor_configuration_unid_snn_date, + hf_cip_ssupervisor_configuration_unid_snn_time, + hf_cip_ssupervisor_configuration_unid_nodeid, + ett_ssupervisor_configuration_unid, + ett_ssupervisor_configuration_unid_snn); + return 10; +} + +static int dissect_s_supervisor_safety_configuration_id(packet_info *pinfo, proto_tree *tree _U_, proto_item *item, + tvbuff_t *tvb, int offset, int total_len) +{ + if (total_len < 10) + { + expert_add_info(pinfo, item, &ei_mal_ssupervisor_safety_configuration_id); + return total_len; + } + + proto_tree_add_item(tree, hf_cip_ssupervisor_safety_configuration_id_sccrc, tvb, offset, 4, ENC_LITTLE_ENDIAN); + + dissect_cipsafety_snn(tree, tvb, pinfo, offset + 4, + hf_cip_ssupervisor_safety_configuration_id_snn_timestamp, + hf_cip_ssupervisor_safety_configuration_id_snn_date, + hf_cip_ssupervisor_safety_configuration_id_snn_time); + + return 10; +} + +static int dissect_s_supervisor_target_unid(packet_info *pinfo, proto_tree *tree _U_, proto_item *item, + tvbuff_t *tvb, int offset, int total_len) +{ + if (total_len < 10) + { + expert_add_info(pinfo, item, &ei_mal_ssupervisor_target_unid); + return total_len; + } + + dissect_unid(tvb, pinfo, offset, item, "TUNID SNN", + hf_cip_ssupervisor_target_unid_snn_timestamp, + hf_cip_ssupervisor_target_unid_snn_date, + hf_cip_ssupervisor_target_unid_snn_time, + hf_cip_ssupervisor_target_unid_nodeid, + ett_ssupervisor_target_unid, + ett_ssupervisor_target_unid_snn); + return 10; +} + +static int dissect_s_supervisor_output_connection_point_owners(packet_info *pinfo, proto_tree *tree, proto_item *item, + tvbuff_t *tvb, int offset, int total_len) +{ + guint16 i, num_entries; + proto_item *entry_item, *app_path_item; + proto_tree *entry_tree, *epath_tree; + int attr_len = 0, app_path_size; + + if (total_len < 2) + { + expert_add_info(pinfo, item, &ei_mal_ssupervisor_cp_owners); + return total_len; + } + + entry_item = proto_tree_add_item(tree, hf_cip_ssupervisor_cp_owners_num_entries, + tvb, offset, 2, ENC_LITTLE_ENDIAN ); + num_entries = tvb_get_letohs(tvb, offset); + attr_len += 2; + + if (num_entries > 0) + { + entry_tree = proto_item_add_subtree(entry_item, ett_ssupervisor_output_cp_owners); + + for (i = 0; i < num_entries; i++) + { + if (total_len < attr_len+11) + { + expert_add_info(pinfo, item, &ei_mal_ssupervisor_cp_owners_entry); + return total_len; + } + + dissect_unid(tvb, pinfo, offset+attr_len, entry_item, "OCPUNID SNN", + hf_cip_ssupervisor_output_cp_owners_ocpunid_snn_timestamp, + hf_cip_ssupervisor_output_cp_owners_ocpunid_snn_date, + hf_cip_ssupervisor_output_cp_owners_ocpunid_snn_time, + hf_cip_ssupervisor_output_cp_owners_ocpunid_nodeid, + ett_ssupervisor_output_cp_owners_ocpunid, + ett_ssupervisor_output_cp_owners_ocpunid_snn); + attr_len += 10; + + proto_tree_add_item(entry_tree, hf_cip_ssupervisor_cp_owners_app_path_size, + tvb, offset+attr_len, 1, ENC_LITTLE_ENDIAN ); + app_path_size = tvb_get_guint8( tvb, offset+attr_len); + attr_len += 1; + + if (total_len < attr_len+app_path_size) + { + expert_add_info(pinfo, item, &ei_mal_ssupervisor_cp_owners_app_path_size); + return total_len; + } + + epath_tree = proto_tree_add_subtree(entry_tree, + tvb, offset+attr_len, app_path_size, ett_path, &app_path_item, "Application Resource: "); + dissect_epath(tvb, pinfo, epath_tree, app_path_item, offset+attr_len, app_path_size, FALSE, TRUE, NULL, NULL, NO_DISPLAY, NULL, FALSE); + attr_len += app_path_size; + } + } + + return attr_len; +} + +static int dissect_s_supervisor_proposed_tunid(packet_info *pinfo, proto_tree *tree _U_, proto_item *item, + tvbuff_t *tvb, int offset, int total_len) +{ + if (total_len < 10) + { + expert_add_info(pinfo, item, &ei_mal_ssupervisor_proposed_tunid); + return total_len; + } + + dissect_unid(tvb, pinfo, offset, item, "Proposed TUNID SNN", + hf_cip_ssupervisor_proposed_tunid_snn_timestamp, + hf_cip_ssupervisor_proposed_tunid_snn_date, + hf_cip_ssupervisor_proposed_tunid_snn_time, + hf_cip_ssupervisor_proposed_tunid_nodeid, + ett_ssupervisor_proposed_tunid, + ett_ssupervisor_proposed_tunid_snn); + + return 10; +} + +/************************************************ + * + * Dissector for CIP Safety Validator Object + * + ************************************************/ +static int dissect_s_validator_type(packet_info *pinfo, proto_tree *tree, proto_item *item, tvbuff_t *tvb, + int offset, int total_len) +{ + if (total_len < 1) + { + expert_add_info(pinfo, item, &ei_mal_svalidator_type); + return total_len; + } + + static int* const bits[] = { + &hf_cip_svalidator_type_pc, + &hf_cip_svalidator_type_conn_type, + NULL + }; + proto_tree_add_bitmask(tree, tvb, offset, hf_cip_svalidator_type, ett_svalidator_type, bits, ENC_LITTLE_ENDIAN); + + return 1; +} + +static int dissect_s_validator_time_coord_msg_min_mult(packet_info *pinfo, proto_tree *tree, proto_item *item, + tvbuff_t *tvb, int offset, int total_len) +{ + int i, size; + + proto_tree_add_item(tree, hf_cip_svalidator_time_coord_msg_min_mult_size, + tvb, offset, 1, ENC_LITTLE_ENDIAN ); + size = tvb_get_guint8( tvb, offset )*2; + + if (total_len < size+1) + { + expert_add_info(pinfo, item, &ei_mal_svalidator_time_coord_msg_min_mult); + return total_len; + } + + for (i = 0; i < size; i+=2) + { + proto_tree_add_item(tree, hf_cip_svalidator_time_coord_msg_min_mult_item, + tvb, offset+1+i, 2, ENC_LITTLE_ENDIAN ); + } + + return (size+1); +} + +static int dissect_s_validator_network_time_multiplier(packet_info *pinfo, proto_tree *tree, proto_item *item, + tvbuff_t *tvb, int offset, int total_len) +{ + int i, size; + + proto_tree_add_item(tree, hf_cip_svalidator_network_time_multiplier_size, + tvb, offset, 1, ENC_LITTLE_ENDIAN ); + size = tvb_get_guint8( tvb, offset )*2; + + if (total_len < size+1) + { + expert_add_info(pinfo, item, &ei_mal_svalidator_network_time_multiplier); + return total_len; + } + + for (i = 0; i < size; i+=2) + { + proto_tree_add_item(tree, hf_cip_svalidator_network_time_multiplier_item, + tvb, offset+1+i, 2, ENC_LITTLE_ENDIAN ); + } + + return (size+1); +} + +static int dissect_s_validator_timeout_multiplier(packet_info *pinfo, proto_tree *tree, proto_item *item, tvbuff_t *tvb, + int offset, int total_len) +{ + int i, size; + + proto_tree_add_item(tree, hf_cip_svalidator_timeout_multiplier_size, + tvb, offset, 1, ENC_LITTLE_ENDIAN ); + size = tvb_get_guint8( tvb, offset ); + + if (total_len < size+1) + { + expert_add_info(pinfo, item, &ei_mal_svalidator_timeout_multiplier); + return total_len; + } + + for (i = 0; i < size; i++) + { + proto_tree_add_item(tree, hf_cip_svalidator_timeout_multiplier_item, + tvb, offset+1+i, 1, ENC_LITTLE_ENDIAN ); + } + + return (size+1); +} + +static int dissect_s_validator_coordination_conn_inst(packet_info *pinfo, proto_tree *tree, proto_item *item, + tvbuff_t *tvb, int offset, int total_len) +{ + int i, size; + + proto_tree_add_item(tree, hf_cip_svalidator_coordination_conn_inst_size, + tvb, offset, 1, ENC_LITTLE_ENDIAN ); + size = tvb_get_guint8( tvb, offset )*2; + + if (total_len < size+1) + { + expert_add_info(pinfo, item, &ei_mal_svalidator_coordination_conn_inst); + return total_len; + } + + for (i = 0; i < size; i+=2) + { + proto_tree_add_item(tree, hf_cip_svalidator_coordination_conn_inst_item, + tvb, offset+1+i, 2, ENC_LITTLE_ENDIAN ); + } + + return (size+1); +} + +static int dissect_s_validator_app_data_path(packet_info *pinfo, proto_tree *tree, + proto_item *item _U_, tvbuff_t *tvb, int offset, int total_len) +{ + proto_item* pi; + proto_tree* epath_tree = proto_tree_add_subtree(tree, tvb, 0, 0, ett_path, &pi, "Application Data Path: "); + dissect_epath(tvb, pinfo, epath_tree, pi, offset, total_len, FALSE, FALSE, NULL, NULL, NO_DISPLAY, NULL, FALSE); + return total_len; +} + +static int dissect_s_validator_prod_cons_fault_count(packet_info *pinfo, proto_tree *tree, proto_item *item, + tvbuff_t *tvb, int offset, int total_len) +{ + int i, size; + + proto_tree_add_item(tree, hf_cip_svalidator_prod_cons_fault_count_size, + tvb, offset, 1, ENC_LITTLE_ENDIAN ); + size = tvb_get_guint8( tvb, offset ); + + if (total_len < size+1) + { + expert_add_info(pinfo, item, &ei_mal_svalidator_prod_cons_fault_count); + return total_len; + } + + for (i = 0; i < size; i++) + { + proto_tree_add_item(tree, hf_cip_svalidator_prod_cons_fault_count_item, + tvb, offset+1+i, 1, ENC_LITTLE_ENDIAN ); + } + + return (size+1); +} + +static void +dissect_cip_s_validator_data( proto_tree *item_tree, + tvbuff_t *tvb, int offset, int item_length, packet_info *pinfo ) +{ + proto_item *pi, *rrsc_item; + proto_tree *rrsc_tree, *cmd_data_tree; + int req_path_size; + guint8 service, gen_status, add_stat_size; + cip_simple_request_info_t req_data; + + col_set_str(pinfo->cinfo, COL_PROTOCOL, "CIPS Validator"); + + /* Add Service code & Request/Response tree */ + service = tvb_get_guint8( tvb, offset ); + rrsc_tree = proto_tree_add_subtree( item_tree, tvb, offset, 1, ett_svalidator_rrsc, &rrsc_item, "Service: " ); + + /* Add Request/Response */ + proto_tree_add_item( rrsc_tree, hf_cip_reqrsp, tvb, offset, 1, ENC_LITTLE_ENDIAN ); + + proto_item_append_text( rrsc_item, "%s (%s)", + val_to_str( ( service & CIP_SC_MASK ), + cip_sc_vals_svalidator , "Unknown Service (0x%02x)"), + val_to_str_const( ( service & CIP_SC_RESPONSE_MASK )>>7, + cip_sc_rr, "") ); + + /* Add Service code */ + proto_tree_add_item(rrsc_tree, hf_cip_svalidator_sc, tvb, offset, 1, ENC_LITTLE_ENDIAN ); + + load_cip_request_data(pinfo, &req_data); + + if (service & CIP_SC_RESPONSE_MASK) + { + /* Response message */ + + /* Add additional status size */ + gen_status = tvb_get_guint8( tvb, offset+2 ); + add_stat_size = tvb_get_guint8( tvb, offset+3 ) * 2; + + /* If there is any command specific data create a sub-tree for it */ + if( ( item_length-4-add_stat_size ) != 0 ) + { + cmd_data_tree = proto_tree_add_subtree( item_tree, tvb, offset+4+add_stat_size, item_length-4-add_stat_size, + ett_ssupervisor_cmd_data, &pi, "Command Specific Data" ); + + if( gen_status == CI_GRC_SUCCESS ) + { + /* Success responses */ + if (((service & CIP_SC_MASK) == SC_GET_ATT_ALL) && + (req_data.iInstance != SEGMENT_VALUE_NOT_SET) && + (req_data.iInstance != 0)) + { + dissect_cip_get_attribute_all_rsp(tvb, pinfo, cmd_data_tree, offset + 4 + add_stat_size, &req_data); + } + else + { + /* Add data */ + proto_tree_add_item(cmd_data_tree, hf_cip_data, + tvb, offset+4+add_stat_size, item_length-4-add_stat_size, ENC_NA); + } + } + else + { + /* Error responses */ + + /* Add data */ + proto_tree_add_item(cmd_data_tree, hf_cip_data, + tvb, offset+4+add_stat_size, item_length-4-add_stat_size, ENC_NA); + } /* end of if-else( CI_CRC_SUCCESS ) */ + + } /* End of if command-specific data present */ + + } /* End of if reply */ + else + { + /* Request message */ + + req_path_size = tvb_get_guint8( tvb, offset+1 )*2; + + /* If there is any command specific data create a sub-tree for it */ + if( (item_length-req_path_size-2) != 0 ) + { + cmd_data_tree = proto_tree_add_subtree( item_tree, tvb, offset+2+req_path_size, item_length-req_path_size-2, + ett_ssupervisor_cmd_data, NULL, "Command Specific Data" ); + proto_tree_add_item(cmd_data_tree, hf_cip_data, + tvb, offset+2+req_path_size, item_length-req_path_size-2, ENC_NA); + } + + } + + add_cip_service_to_info_column(pinfo, service, cip_sc_vals_svalidator); +} + +static int +dissect_cip_class_s_validator(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) +{ + proto_item *ti; + proto_tree *class_tree; + + /* Create display subtree for the protocol */ + ti = proto_tree_add_item(tree, proto_cip_class_s_validator, tvb, 0, -1, ENC_NA); + class_tree = proto_item_add_subtree( ti, ett_cip_class_s_validator ); + + dissect_cip_s_validator_data( class_tree, tvb, 0, tvb_reported_length(tvb), pinfo ); + + return tvb_reported_length(tvb); +} + +static gboolean +dissect_class_svalidator_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) +{ + unsigned char service, service_code, ioilen, segment; + cip_req_info_t* preq_info; + guint32 classid = 0; + int offset = 0; + + service = tvb_get_guint8( tvb, offset ); + service_code = service & CIP_SC_MASK; + + /* Handle GetAttributeAll and SetAttributeAll in CCO class */ + if (service_code == SC_GET_ATT_ALL) + { + if (service & CIP_SC_RESPONSE_MASK) + { + /* Service response */ + preq_info = (cip_req_info_t*)p_get_proto_data(wmem_file_scope(), pinfo, proto_cip, 0); + if ((preq_info != NULL) && + (preq_info->dissector == dissector_get_uint_handle( subdissector_class_table, CI_CLS_SAFETY_VALIDATOR))) + { + call_dissector(preq_info->dissector, tvb, pinfo, tree); + return TRUE; + } + } + else + { + /* Service request */ + ioilen = tvb_get_guint8( tvb, offset + 1 ); + if (ioilen > 1) + { + segment = tvb_get_guint8( tvb, offset + 2 ); + if (((segment & CI_SEGMENT_TYPE_MASK) == CI_LOGICAL_SEGMENT) && + ((segment & CI_LOGICAL_SEG_TYPE_MASK) == CI_LOGICAL_SEG_CLASS_ID)) + { + /* Logical Class ID, do a format check */ + switch ( segment & CI_LOGICAL_SEG_FORMAT_MASK ) + { + case CI_LOGICAL_SEG_8_BIT: + classid = tvb_get_guint8( tvb, offset + 3 ); + break; + case CI_LOGICAL_SEG_16_BIT: + if ( ioilen >= 2 ) + classid = tvb_get_letohs( tvb, offset + 4 ); + break; + case CI_LOGICAL_SEG_32_BIT: + if ( ioilen >= 3 ) + classid = tvb_get_letohl( tvb, offset + 4 ); + break; + } + } + } + + if (classid == CI_CLS_SAFETY_VALIDATOR) + { + call_dissector(cip_class_s_validator_handle, tvb, pinfo, tree ); + return TRUE; + } + + } + } + + return FALSE; +} + +/************************************************ + * + * CRC handling + * + ************************************************/ +static guint8 compute_crc_s1_pid(const cip_connection_triad_t* triad) +{ + guint8 temp_buf[8]; + memcpy(temp_buf, &triad->VendorID, 2); + memcpy(&temp_buf[2], &triad->DeviceSerialNumber, 4); + memcpy(&temp_buf[6], &triad->ConnSerialNumber, 2); + + return crc8_0x37(temp_buf, 8, 0); +} + +static guint8 compute_crc_s1_timestamp(guint8 pid_seed, guint8 mode_byte_mask, guint16 timestamp) +{ + guint8 mode_byte_crc = crc8_0x37(&mode_byte_mask, 1, pid_seed); + guint8 timestamp_crc = crc8_0x37((guint8*)×tamp, 2, mode_byte_crc); + + return timestamp_crc; +} + +static guint8 compute_crc_s1_data(guint8 pid_seed, guint8 mode_byte_mask, const guint8 *buf, int len) +{ + guint8 mode_byte_crc = crc8_0x37(&mode_byte_mask, 1, pid_seed); + + return crc8_0x37(buf, len, mode_byte_crc); +} + +static guint8 compute_crc_s2_data(guint8 pid_seed, guint8 mode_byte_mask, guint8 *comp_buf, int len) +{ + int i; + guint8 mode_byte_crc = crc8_0x3B(&mode_byte_mask, 1, pid_seed); + + for (i = 0; i < len; i++) + comp_buf[i] ^= 0xFF; + + return crc8_0x3B(comp_buf, len, mode_byte_crc); +} + +static guint16 compute_crc_s3_pid(const cip_connection_triad_t* triad) +{ + guint8 temp_buf[8]; + memcpy(temp_buf, &triad->VendorID, 2); + memcpy(&temp_buf[2], &triad->DeviceSerialNumber, 4); + memcpy(&temp_buf[6], &triad->ConnSerialNumber, 2); + + return crc16_0x080F_seed(temp_buf, 8, 0); +} + +static guint16 compute_crc_s3_base_data(guint16 pid_seed, guint8 mode_byte_mask, const guint8 *buf, int len) +{ + guint16 mode_byte_crc = crc16_0x080F_seed(&mode_byte_mask, 1, pid_seed); + + return crc16_0x080F_seed(buf, len, mode_byte_crc); +} + +static guint16 compute_crc_s3_extended_data(guint16 pid_seed, guint16 rollover_value, guint8 mode_byte_mask, const guint8 *buf, int len) +{ + guint16 rollover_crc = crc16_0x080F_seed((guint8*)&rollover_value, 2, pid_seed); + guint16 mode_byte_crc = crc16_0x080F_seed(&mode_byte_mask, 1, rollover_crc); + + return crc16_0x080F_seed(buf, len, mode_byte_crc); +} + +static guint16 compute_crc_s3_time(guint16 pid_seed, guint8 ack_mcast_byte, guint16 timestamp_value) +{ + guint16 mode_byte_crc = crc16_0x080F_seed(&ack_mcast_byte, 1, pid_seed); + guint16 timestamp_crc; + + timestamp_crc = crc16_0x080F_seed((guint8*)×tamp_value, 2, mode_byte_crc); + + return timestamp_crc; +} + +static guint32 compute_crc_s5_pid(const cip_connection_triad_t* triad) +{ + guint8 temp_buf[8]; + memcpy(temp_buf, &triad->VendorID, 2); + memcpy(&temp_buf[2], &triad->DeviceSerialNumber, 4); + memcpy(&temp_buf[6], &triad->ConnSerialNumber, 2); + + return crc32_0x5D6DCB_seed(temp_buf, 8, 0); +} + +static guint32 compute_crc_s5_short_data(guint32 pid_seed, guint16 rollover_value, guint8 mode_byte_mask, guint16 timestamp_value, const guint8 *buf, int len) +{ + guint32 rollover_crc = crc32_0x5D6DCB_seed((guint8*)&rollover_value, 2, pid_seed); + guint32 mode_byte_crc = crc32_0x5D6DCB_seed(&mode_byte_mask, 1, rollover_crc); + guint32 data_crc, timestamp_crc; + + data_crc = crc32_0x5D6DCB_seed(buf, len, mode_byte_crc); + timestamp_crc = crc32_0x5D6DCB_seed((guint8*)×tamp_value, 2, data_crc); + + return timestamp_crc; +} + +static guint32 compute_crc_s5_long_data(guint32 pid_seed, guint16 rollover_value, guint8 mode_byte_mask, guint16 timestamp_value, guint8 *comp_buf, int len) +{ + int i; + guint32 rollover_crc = crc32_0x5D6DCB_seed((guint8*)&rollover_value, 2, pid_seed); + guint32 mode_byte_crc = crc32_0x5D6DCB_seed(&mode_byte_mask, 1, rollover_crc); + guint32 comp_data_crc, timestamp_crc; + + for (i = 0; i < len; i++) + comp_buf[i] ^= 0xFF; + + comp_data_crc = crc32_0x5D6DCB_seed(comp_buf, len, mode_byte_crc); + timestamp_crc = crc32_0x5D6DCB_seed((guint8*)×tamp_value, 2, comp_data_crc); + + return timestamp_crc; +} + +static guint32 compute_crc_s5_time(guint32 pid_seed, guint8 ack_mcast_byte, guint16 timestamp_value) +{ + guint32 mode_byte_crc = crc32_0x5D6DCB_seed(&ack_mcast_byte, 1, pid_seed); + guint32 timestamp_crc; + + timestamp_crc = crc32_0x5D6DCB_seed((guint8*)×tamp_value, 2, mode_byte_crc); + + return timestamp_crc; +} + +static gboolean verify_compliment_data(tvbuff_t *tvb, int data_offset, int complement_data_offset, int data_size) +{ + const guint8 *data = tvb_get_ptr(tvb, data_offset, data_size); + const guint8 *complement_data = tvb_get_ptr(tvb, complement_data_offset, data_size); + int i; + + for (i = 0; i < data_size; i++) + { + if ((data[i] ^ complement_data[i])!= 0xFF) + return FALSE; + } + + return TRUE; +} + +static void validate_crc_s5(packet_info* pinfo, proto_tree* tree, tvbuff_t* tvb, gboolean compute_crc, + guint32 crc_s5_0, guint32 crc_s5_1, guint32 crc_s5_2, guint32 computed_crc_s5) +{ + proto_item* crc_s5_status_item; + + /* CRC-S5 doesn't use proto_tree_add_checksum because the checksum is broken up into multiple fields */ + if (compute_crc) + { + guint32 value_s5 = crc_s5_0; + value_s5 += ((crc_s5_1 << 8) & 0xFF00); + value_s5 += ((crc_s5_2 << 16) & 0xFF0000); + + if (computed_crc_s5 == value_s5) + { + crc_s5_status_item = proto_tree_add_uint(tree, hf_cipsafety_crc_s5_status, tvb, 0, 0, PROTO_CHECKSUM_E_GOOD); + } + else + { + crc_s5_status_item = proto_tree_add_uint(tree, hf_cipsafety_crc_s5_status, tvb, 0, 0, PROTO_CHECKSUM_E_BAD); + expert_add_info_format(pinfo, crc_s5_status_item, &ei_cipsafety_crc_s5, "%s [should be 0x%08x]", expert_get_summary(&ei_cipsafety_crc_s5), computed_crc_s5); + } + } + else + { + crc_s5_status_item = proto_tree_add_uint(tree, hf_cipsafety_crc_s5_status, tvb, 0, 0, PROTO_CHECKSUM_E_UNVERIFIED); + } + + proto_item_set_generated(crc_s5_status_item); +} + +/************************************************ + * + * Dissector for CIP Safety I/O Data + * + ************************************************/ +static void +dissect_mode_byte( proto_tree *tree, tvbuff_t *tvb, int offset, packet_info *pinfo) +{ + proto_item *mode_item, *run_idle_item, *tbd_item, *tbd2_item; + proto_tree *mode_tree; + guint8 mode_byte; + + mode_byte = tvb_get_guint8(tvb, offset); + + /* dissect Mode Byte bits */ + mode_item = proto_tree_add_item(tree, hf_cipsafety_mode_byte, tvb, offset, 1, ENC_LITTLE_ENDIAN); + mode_tree = proto_item_add_subtree( mode_item, ett_cipsafety_mode_byte); + + proto_tree_add_item(mode_tree, hf_cipsafety_mode_byte_ping_count, tvb, offset, 1, ENC_LITTLE_ENDIAN); + proto_tree_add_item(mode_tree, hf_cipsafety_mode_byte_not_tbd, tvb, offset, 1, ENC_LITTLE_ENDIAN); + proto_tree_add_item(mode_tree, hf_cipsafety_mode_byte_tbd_2_copy, tvb, offset, 1, ENC_LITTLE_ENDIAN); + proto_tree_add_item(mode_tree, hf_cipsafety_mode_byte_not_run_idle, tvb, offset, 1, ENC_LITTLE_ENDIAN); + tbd_item = proto_tree_add_item(mode_tree, hf_cipsafety_mode_byte_tbd, tvb, offset, 1, ENC_LITTLE_ENDIAN); + tbd2_item = proto_tree_add_item(mode_tree, hf_cipsafety_mode_byte_tbd_2_bit, tvb, offset, 1, ENC_LITTLE_ENDIAN); + run_idle_item = proto_tree_add_item(mode_tree, hf_cipsafety_mode_byte_run_idle, tvb, offset, 1, ENC_LITTLE_ENDIAN); + + /* verify Mode Byte bits */ + /* TBD */ + if ((((mode_byte & 0x20) >> 5) & 0x01) == (((mode_byte & 0x04) >> 2) & 0x01)) + expert_add_info(pinfo, tbd_item, &ei_cipsafety_tbd_not_complemented); + + /* TBD 2 */ + if ((((mode_byte & 0x40) >> 6) & 0x01) != (((mode_byte & 0x08) >> 3) & 0x01)) + expert_add_info(pinfo, tbd2_item, &ei_cipsafety_tbd2_not_copied); + + /* Run/Idle */ + if ((((mode_byte & 0x80) >> 7) & 0x01) == (((mode_byte & 0x10) >> 4) & 0x01)) + expert_add_info(pinfo, run_idle_item, &ei_cipsafety_run_idle_not_complemented); +} + +static void +dissect_ack_byte( proto_tree *tree, tvbuff_t *tvb, int offset) +{ + // TODO: add ack_byte validation + static int* const bits[] = { + &hf_cipsafety_ack_byte_ping_count_reply, + &hf_cipsafety_ack_byte_reserved1, + &hf_cipsafety_ack_byte_ping_response, + &hf_cipsafety_ack_byte_reserved2, + &hf_cipsafety_ack_byte_parity_even, + NULL + }; + + proto_tree_add_bitmask(tree, tvb, offset, hf_cipsafety_ack_byte, ett_cipsafety_ack_byte, bits, ENC_LITTLE_ENDIAN); +} + +static void +dissect_mcast_byte( proto_tree *tree, tvbuff_t *tvb, int offset) +{ + // TODO: add mcast_byte validation + static int* const bits[] = { + &hf_cipsafety_mcast_byte_consumer_num, + &hf_cipsafety_mcast_byte_reserved1, + &hf_cipsafety_mcast_byte_mai, + &hf_cipsafety_mcast_byte_reserved2, + &hf_cipsafety_mcast_byte_parity_even, + NULL + }; + + proto_tree_add_bitmask(tree, tvb, offset, hf_cipsafety_mcast_byte, ett_cipsafety_mcast_byte, bits, ENC_LITTLE_ENDIAN); +} + +// Base Format Time Correction Message +static void dissect_base_format_time_correction_message(proto_tree* tree, tvbuff_t* tvb, int offset) +{ + dissect_mcast_byte(tree, tvb, offset); + proto_tree_add_item(tree, hf_cipsafety_time_correction, tvb, offset + 1, 2, ENC_LITTLE_ENDIAN); + proto_tree_add_item(tree, hf_cipsafety_mcast_byte2, tvb, offset + 3, 1, ENC_LITTLE_ENDIAN); + proto_tree_add_item(tree, hf_cipsafety_crc_s3, tvb, offset + 4, 2, ENC_LITTLE_ENDIAN); +} + +// Extended Format Time Correction Message +static void dissect_extended_format_time_correction_message(proto_tree* tree, tvbuff_t* tvb, int offset) +{ + dissect_mcast_byte(tree, tvb, offset); + proto_tree_add_item(tree, hf_cipsafety_time_correction, tvb, offset + 1, 2, ENC_LITTLE_ENDIAN); + proto_tree_add_item(tree, hf_cipsafety_crc_s5_0, tvb, offset + 3, 1, ENC_LITTLE_ENDIAN); + proto_tree_add_item(tree, hf_cipsafety_crc_s5_1, tvb, offset + 4, 1, ENC_LITTLE_ENDIAN); + proto_tree_add_item(tree, hf_cipsafety_crc_s5_2, tvb, offset + 5, 1, ENC_LITTLE_ENDIAN); + + // TODO: Validate CRC S5. +} + +// Base Format, Time Stamp Section Format +static void dissect_base_format_time_stamp_section(packet_info* pinfo, proto_tree* tree, tvbuff_t* tvb, int offset, + gboolean compute_crc, guint8 mode_byte, const cip_connection_triad_t* connection_triad) +{ + proto_tree_add_item(tree, hf_cipsafety_timestamp, tvb, offset, 2, ENC_LITTLE_ENDIAN); + guint16 timestamp = tvb_get_letohs(tvb, offset); + + if (compute_crc) + { + guint8 computed_crc_s1 = compute_crc_s1_timestamp(compute_crc_s1_pid(connection_triad), + (mode_byte & MODE_BYTE_CRC_S1_TIME_STAMP_MASK), + timestamp); + proto_tree_add_checksum(tree, tvb, offset + 2, + hf_cipsafety_crc_s1, hf_cipsafety_crc_s1_status, &ei_cipsafety_crc_s1, pinfo, + computed_crc_s1, ENC_LITTLE_ENDIAN, PROTO_CHECKSUM_VERIFY); + } + else + { + proto_tree_add_checksum(tree, tvb, offset + 2, + hf_cipsafety_crc_s1, hf_cipsafety_crc_s1_status, &ei_cipsafety_crc_s1, + pinfo, 0, ENC_LITTLE_ENDIAN, PROTO_CHECKSUM_NO_FLAGS); + } +} + +// Base Format Time Coordination Message +// Note: All data starts from the beginning of the tvb buffer. +static void dissect_base_format_time_coordination_message(packet_info* pinfo, proto_tree* tree, tvbuff_t* tvb, + gboolean compute_crc, const cip_connection_triad_t* connection_triad) +{ + dissect_ack_byte(tree, tvb, 0); + guint8 ack_byte = tvb_get_guint8(tvb, 0); + + proto_tree_add_item(tree, hf_cipsafety_consumer_time_value, tvb, 1, 2, ENC_LITTLE_ENDIAN); + guint16 timestamp = tvb_get_letohs(tvb, 1); + + proto_tree_add_item(tree, hf_cipsafety_ack_byte2, tvb, 3, 1, ENC_LITTLE_ENDIAN); + + if (compute_crc) + { + guint16 computed_crc_s3 = compute_crc_s3_time(compute_crc_s3_pid(connection_triad), ack_byte, timestamp); + proto_tree_add_checksum(tree, tvb, 4, + hf_cipsafety_crc_s3, hf_cipsafety_crc_s3_status, &ei_cipsafety_crc_s3, pinfo, + computed_crc_s3, ENC_LITTLE_ENDIAN, PROTO_CHECKSUM_VERIFY); + } + else + { + proto_tree_add_checksum(tree, tvb, 4, + hf_cipsafety_crc_s3, hf_cipsafety_crc_s3_status, &ei_cipsafety_crc_s3, + pinfo, 0, ENC_LITTLE_ENDIAN, PROTO_CHECKSUM_NO_FLAGS); + } +} + +// Extended Format Time Coordination Message +// Note: All data starts from the beginning of the tvb buffer. +static void dissect_extended_format_time_coordination_message(packet_info* pinfo, proto_tree* tree, tvbuff_t* tvb, + gboolean compute_crc, const cip_connection_triad_t* connection_triad) +{ + dissect_ack_byte(tree, tvb, 0); + guint8 ack_byte = tvb_get_guint8(tvb, 0); + + proto_tree_add_item(tree, hf_cipsafety_consumer_time_value, tvb, 1, 2, ENC_LITTLE_ENDIAN); + guint16 timestamp = tvb_get_letohs(tvb, 1); + + guint32 crc_s5_0, crc_s5_1, crc_s5_2; + proto_tree_add_item_ret_uint(tree, hf_cipsafety_crc_s5_0, tvb, 3, 1, ENC_LITTLE_ENDIAN, &crc_s5_0); + proto_tree_add_item_ret_uint(tree, hf_cipsafety_crc_s5_1, tvb, 4, 1, ENC_LITTLE_ENDIAN, &crc_s5_1); + proto_tree_add_item_ret_uint(tree, hf_cipsafety_crc_s5_2, tvb, 5, 1, ENC_LITTLE_ENDIAN, &crc_s5_2); + + guint32 computed_crc_s5 = compute_crc_s5_time(compute_crc_s5_pid(connection_triad), + ack_byte, + timestamp); + validate_crc_s5(pinfo, tree, tvb, compute_crc, crc_s5_0, crc_s5_1, crc_s5_2, computed_crc_s5); +} + +// 1 or 2 Byte Data section, Base Format +// Note: All data starts from the beginning of the tvb buffer. +static void dissect_base_format_1_or_2_byte_data(packet_info* pinfo, proto_tree* tree, tvbuff_t* tvb, int io_data_size, + gboolean compute_crc, const cip_connection_triad_t* connection_triad) +{ + proto_tree_add_item(tree, hf_cipsafety_data, tvb, 0, io_data_size, ENC_NA); + dissect_mode_byte(tree, tvb, io_data_size, pinfo); + guint8 mode_byte = tvb_get_guint8(tvb, io_data_size); + + if (compute_crc) + { + guint8 computed_crc_s1 = compute_crc_s1_data(compute_crc_s1_pid(connection_triad), + (mode_byte & MODE_BYTE_CRC_S1_MASK), + tvb_get_ptr(tvb, 0, io_data_size), io_data_size); + + proto_tree_add_checksum(tree, tvb, io_data_size + 1, + hf_cipsafety_crc_s1, hf_cipsafety_crc_s1_status, &ei_cipsafety_crc_s1, pinfo, + computed_crc_s1, ENC_LITTLE_ENDIAN, PROTO_CHECKSUM_VERIFY); + + guint8 computed_crc_s2 = compute_crc_s2_data(compute_crc_s1_pid(connection_triad), + ((mode_byte ^ 0xFF) & MODE_BYTE_CRC_S1_MASK), + /* I/O data is duplicated because it will be complemented inline */ + (guint8*)tvb_memdup(wmem_packet_scope(), tvb, 0, io_data_size), io_data_size); + + proto_tree_add_checksum(tree, tvb, io_data_size + 2, + hf_cipsafety_crc_s2, hf_cipsafety_crc_s2_status, &ei_cipsafety_crc_s2, pinfo, + computed_crc_s2, ENC_LITTLE_ENDIAN, PROTO_CHECKSUM_VERIFY); + } + else + { + proto_tree_add_checksum(tree, tvb, io_data_size + 1, + hf_cipsafety_crc_s1, hf_cipsafety_crc_s1_status, &ei_cipsafety_crc_s1, + pinfo, 0, ENC_LITTLE_ENDIAN, PROTO_CHECKSUM_NO_FLAGS); + proto_tree_add_checksum(tree, tvb, io_data_size + 2, + hf_cipsafety_crc_s2, hf_cipsafety_crc_s2_status, &ei_cipsafety_crc_s2, + pinfo, 0, ENC_LITTLE_ENDIAN, PROTO_CHECKSUM_NO_FLAGS); + } +} + +// 3 to 250 Byte Data section, Base Format +// Note: All data starts from the beginning of the tvb buffer. +static void dissect_base_format_3_to_250_byte_data(packet_info* pinfo, proto_tree* tree, tvbuff_t* tvb, int io_data_size, + gboolean compute_crc, const cip_connection_triad_t* connection_triad) +{ + proto_tree_add_item(tree, hf_cipsafety_data, tvb, 0, io_data_size, ENC_NA); + dissect_mode_byte(tree, tvb, io_data_size, pinfo); + guint mode_byte = tvb_get_guint8(tvb, io_data_size); + + if (compute_crc) + { + guint16 computed_crc_s3 = compute_crc_s3_base_data(compute_crc_s3_pid(connection_triad), + mode_byte & MODE_BYTE_CRC_S3_MASK, tvb_get_ptr(tvb, 0, io_data_size), io_data_size); + + proto_tree_add_checksum(tree, tvb, io_data_size + 1, + hf_cipsafety_crc_s3, hf_cipsafety_crc_s3_status, &ei_cipsafety_crc_s3, pinfo, + computed_crc_s3, ENC_LITTLE_ENDIAN, PROTO_CHECKSUM_VERIFY); + } + else + { + proto_tree_add_checksum(tree, tvb, io_data_size + 1, + hf_cipsafety_crc_s3, hf_cipsafety_crc_s3_status, &ei_cipsafety_crc_s3, + pinfo, 0, ENC_LITTLE_ENDIAN, PROTO_CHECKSUM_NO_FLAGS); + } + + proto_item* complement_item = proto_tree_add_item(tree, hf_cipsafety_complement_data, tvb, io_data_size + 3, io_data_size, ENC_NA); + if (!verify_compliment_data(tvb, 0, io_data_size + 3, io_data_size)) + expert_add_info(pinfo, complement_item, &ei_cipsafety_not_complement_data); + + if (compute_crc) + { + guint16 computed_crc_s3 = compute_crc_s3_base_data(compute_crc_s3_pid(connection_triad), + ((mode_byte ^ 0xFF) & MODE_BYTE_CRC_S3_MASK), + tvb_get_ptr(tvb, io_data_size + 3, io_data_size), io_data_size); + + proto_tree_add_checksum(tree, tvb, (io_data_size * 2) + 3, + hf_cipsafety_complement_crc_s3, hf_cipsafety_complement_crc_s3_status, &ei_cipsafety_complement_crc_s3, pinfo, + computed_crc_s3, ENC_LITTLE_ENDIAN, PROTO_CHECKSUM_VERIFY); + } + else + { + proto_tree_add_checksum(tree, tvb, (io_data_size * 2) + 3, + hf_cipsafety_complement_crc_s3, hf_cipsafety_complement_crc_s3_status, &ei_cipsafety_complement_crc_s3, + pinfo, 0, ENC_LITTLE_ENDIAN, PROTO_CHECKSUM_NO_FLAGS); + } +} + +// 1 or 2 Byte Data Section, Extended Format +// Note: All data starts from the beginning of the tvb buffer. +static void dissect_extended_format_1_or_2_byte_data(packet_info* pinfo, proto_tree* tree, tvbuff_t* tvb, int io_data_size, + gboolean compute_crc, const cip_connection_triad_t* connection_triad, const cip_safety_packet_data_t* packet_data) +{ + proto_tree_add_item(tree, hf_cipsafety_data, tvb, 0, io_data_size, ENC_NA); + dissect_mode_byte(tree, tvb, io_data_size, pinfo); + guint mode_byte = tvb_get_guint8(tvb, io_data_size); + + guint32 crc_s5_0, crc_s5_1, crc_s5_2; + proto_tree_add_item_ret_uint(tree, hf_cipsafety_crc_s5_0, tvb, io_data_size + 1, 1, ENC_LITTLE_ENDIAN, &crc_s5_0); + proto_tree_add_item_ret_uint(tree, hf_cipsafety_crc_s5_1, tvb, io_data_size + 2, 1, ENC_LITTLE_ENDIAN, &crc_s5_1); + proto_tree_add_item(tree, hf_cipsafety_timestamp, tvb, io_data_size + 3, 2, ENC_LITTLE_ENDIAN); + proto_tree_add_item_ret_uint(tree, hf_cipsafety_crc_s5_2, tvb, io_data_size + 5, 1, ENC_LITTLE_ENDIAN, &crc_s5_2); + + guint16 timestamp = tvb_get_letohs(tvb, io_data_size + 3); + + guint32 computed_crc_s5 = 0; + if (packet_data != NULL) + { + computed_crc_s5 = compute_crc_s5_short_data(compute_crc_s5_pid(connection_triad), + packet_data->rollover_value, + mode_byte & MODE_BYTE_CRC_S5_BASE_MASK, + timestamp, + tvb_get_ptr(tvb, 0, io_data_size), + io_data_size); + } + + validate_crc_s5(pinfo, tree, tvb, compute_crc, crc_s5_0, crc_s5_1, crc_s5_2, computed_crc_s5); +} + +// 3 to 250 Byte Data section, Extended Format +// Note: All data starts from the beginning of the tvb buffer. +static void dissect_extended_format_3_to_250_byte_data(packet_info* pinfo, proto_tree* tree, tvbuff_t* tvb, int io_data_size, + gboolean compute_crc, const cip_connection_triad_t* connection_triad, const cip_safety_packet_data_t* packet_data) +{ + proto_tree_add_item(tree, hf_cipsafety_data, tvb, 0, io_data_size, ENC_NA); + dissect_mode_byte(tree, tvb, io_data_size, pinfo); + guint mode_byte = tvb_get_guint8(tvb, io_data_size); + + guint16 timestamp = tvb_get_letohs(tvb, (io_data_size * 2) + 5); + + if (compute_crc) + { + if (packet_data != NULL) + { + guint16 computed_crc_s3 = compute_crc_s3_extended_data(compute_crc_s3_pid(connection_triad), + packet_data->rollover_value, + mode_byte & MODE_BYTE_CRC_S3_MASK, + tvb_get_ptr(tvb, 0, io_data_size), io_data_size); + + proto_tree_add_checksum(tree, tvb, io_data_size + 1, + hf_cipsafety_crc_s3, hf_cipsafety_crc_s3_status, &ei_cipsafety_crc_s3, pinfo, + computed_crc_s3, ENC_LITTLE_ENDIAN, PROTO_CHECKSUM_VERIFY); + } + } + else + { + proto_tree_add_checksum(tree, tvb, io_data_size + 1, + hf_cipsafety_crc_s3, hf_cipsafety_crc_s3_status, &ei_cipsafety_crc_s3, + pinfo, 0, ENC_LITTLE_ENDIAN, PROTO_CHECKSUM_NO_FLAGS); + } + + proto_item* complement_item = proto_tree_add_item(tree, hf_cipsafety_complement_data, tvb, io_data_size + 3, io_data_size, ENC_NA); + if (!verify_compliment_data(tvb, 0, io_data_size + 3, io_data_size)) + expert_add_info(pinfo, complement_item, &ei_cipsafety_not_complement_data); + + guint32 crc_s5_0, crc_s5_1, crc_s5_2; + proto_tree_add_item_ret_uint(tree, hf_cipsafety_crc_s5_0, tvb, (io_data_size * 2) + 3, 1, ENC_LITTLE_ENDIAN, &crc_s5_0); + proto_tree_add_item_ret_uint(tree, hf_cipsafety_crc_s5_1, tvb, (io_data_size * 2) + 4, 1, ENC_LITTLE_ENDIAN, &crc_s5_1); + proto_tree_add_item(tree, hf_cipsafety_timestamp, tvb, (io_data_size * 2) + 5, 2, ENC_LITTLE_ENDIAN); + proto_tree_add_item_ret_uint(tree, hf_cipsafety_crc_s5_2, tvb, (io_data_size * 2) + 7, 1, ENC_LITTLE_ENDIAN, &crc_s5_2); + + guint32 computed_crc_s5 = 0; + if (packet_data != NULL) + { + computed_crc_s5 = compute_crc_s5_long_data(compute_crc_s5_pid(connection_triad), + packet_data->rollover_value, + mode_byte & MODE_BYTE_CRC_S5_EXTENDED_MASK, + timestamp, + /* I/O data is duplicated because it will be complemented inline */ + (guint8*)tvb_memdup(wmem_packet_scope(), tvb, 0, io_data_size), + io_data_size); + } + validate_crc_s5(pinfo, tree, tvb, compute_crc, crc_s5_0, crc_s5_1, crc_s5_2, computed_crc_s5); +} + +// Note: This updates the running timestamp/rollover data in safety_info during the first pass. +static cip_safety_packet_data_t* get_timestamp_packet_data(packet_info* pinfo, cip_safety_info_t* safety_info, guint16 timestamp) +{ + cip_safety_packet_data_t* packet_data = NULL; + + /* Determine if packet timestamp results in rollover count increment */ + if (!pinfo->fd->visited) + { + packet_data = wmem_new0(wmem_file_scope(), cip_safety_packet_data_t); + + if ((timestamp == 0) && !safety_info->eip_conn_info->safety.seen_non_zero_timestamp) + { + // The rollover value is zero, until the Time Coordination exchange is done. + // When the timestamp is zero, that means we haven't seen the Time Coordination message. + packet_data->rollover_value = 0; + } + else + { + safety_info->eip_conn_info->safety.seen_non_zero_timestamp = TRUE; + + if (timestamp < safety_info->eip_conn_info->safety.running_timestamp_value) + { + safety_info->eip_conn_info->safety.running_rollover_value++; + } + + /* Save the rollover value for CRC calculations */ + packet_data->rollover_value = safety_info->eip_conn_info->safety.running_rollover_value; + safety_info->eip_conn_info->safety.running_timestamp_value = timestamp; + } + + p_add_proto_data(wmem_file_scope(), pinfo, proto_cipsafety, 0, packet_data); + } + else + { + packet_data = (cip_safety_packet_data_t*)p_get_proto_data(wmem_file_scope(), pinfo, proto_cipsafety, 0); + } + + return packet_data; +} + +static void +dissect_cip_safety_data( proto_tree *tree, proto_item *item, tvbuff_t *tvb, int item_length, packet_info *pinfo, cip_safety_info_t* safety_info) +{ + int base_length, io_data_size; + gboolean multicast = in4_addr_is_multicast(pntoh32(pinfo->dst.data)); + gboolean server_dir = FALSE; + enum enip_connid_type conn_type = ECIDT_UNKNOWN; + enum cip_safety_format_type format = CIP_SAFETY_BASE_FORMAT; + guint16 timestamp; + guint8 mode_byte; + gboolean short_format = TRUE; + gboolean compute_crc = ((safety_info != NULL) && (safety_info->compute_crc == TRUE)); + cip_connection_triad_t connection_triad = {0}; + + /* Make entries in Protocol column and Info column on summary display */ + col_set_str(pinfo->cinfo, COL_PROTOCOL, "CIP Safety"); + + /* determine the connection type as it affects the fields dissected */ + if (safety_info != NULL && safety_info->eip_conn_info != NULL) + { + conn_type = safety_info->conn_type; + format = safety_info->eip_conn_info->safety.format; + server_dir = (safety_info->eip_conn_info->TransportClass_trigger & CI_PRODUCTION_DIR_MASK) ? TRUE : FALSE; + } + + /* compute the base packet length to determine what is actual I/O data */ + base_length = multicast ? 12 : 6; + + if (item_length < base_length) { + expert_add_info(pinfo, item, &ei_mal_io); + return; + } + + if (((conn_type == ECIDT_O2T) && (server_dir == FALSE)) || + ((conn_type == ECIDT_T2O) && (server_dir == TRUE))) + { + if (compute_crc) + { + if ((conn_type == ECIDT_O2T) && (server_dir == FALSE)) + { + connection_triad = safety_info->eip_conn_info->triad; + } + else + { + connection_triad = safety_info->eip_conn_info->safety.target_triad; + } + } + + /* consumer data */ + + switch (format) + { + case CIP_SAFETY_BASE_FORMAT: + dissect_base_format_time_coordination_message(pinfo, tree, tvb, compute_crc, &connection_triad); + break; + case CIP_SAFETY_EXTENDED_FORMAT: + dissect_extended_format_time_coordination_message(pinfo, tree, tvb, compute_crc, &connection_triad); + break; + } + } + else if (((conn_type == ECIDT_O2T) && (server_dir == TRUE)) || + ((conn_type == ECIDT_T2O) && (server_dir == FALSE))) + { + if (compute_crc) + { + if ((conn_type == ECIDT_O2T) && (server_dir == TRUE)) + { + connection_triad = safety_info->eip_conn_info->triad; + } + else + { + connection_triad = safety_info->eip_conn_info->safety.target_triad; + } + } + + if (item_length-base_length > 2) + short_format = FALSE; + + /* producer data */ + switch (format) + { + case CIP_SAFETY_BASE_FORMAT: + if (short_format) + { + io_data_size = item_length-base_length; + mode_byte = tvb_get_guint8(tvb, io_data_size); + + dissect_base_format_1_or_2_byte_data(pinfo, tree, tvb, io_data_size, compute_crc, &connection_triad); + dissect_base_format_time_stamp_section(pinfo, tree, tvb, io_data_size + 3, compute_crc, mode_byte, &connection_triad); + + if (multicast) + { + dissect_base_format_time_correction_message(tree, tvb, item_length - 6); + } + } + else + { + /* 3 to 250 Byte Data section, Base Format */ + if (item_length%2 == 1) + { + /* Malformed packet */ + expert_add_info(pinfo, item, &ei_mal_io); + return; + } + + io_data_size = multicast ? ((item_length-14)/2) : ((item_length-8)/2); + mode_byte = tvb_get_guint8(tvb, io_data_size); + + dissect_base_format_3_to_250_byte_data(pinfo, tree, tvb, io_data_size, compute_crc, &connection_triad); + dissect_base_format_time_stamp_section(pinfo, tree, tvb, (io_data_size * 2) + 5, compute_crc, mode_byte, &connection_triad); + + if (multicast) + { + dissect_base_format_time_correction_message(tree, tvb, (io_data_size * 2) + 5); + } + } + break; + case CIP_SAFETY_EXTENDED_FORMAT: + { + if (short_format) + { + io_data_size = item_length-base_length; + timestamp = tvb_get_letohs(tvb, io_data_size+3); + } + else + { + io_data_size = multicast ? ((item_length-14)/2) : ((item_length-8)/2); + timestamp = tvb_get_letohs(tvb, (io_data_size*2)+5); + } + + cip_safety_packet_data_t* packet_data = NULL; + if (compute_crc) + { + packet_data = get_timestamp_packet_data(pinfo, safety_info, timestamp); + } + + if (short_format) + { + dissect_extended_format_1_or_2_byte_data(pinfo, tree, tvb, io_data_size, compute_crc, &connection_triad, packet_data); + + if (multicast) + { + dissect_extended_format_time_correction_message(tree, tvb, item_length - 6); + } + } + else + { + /* 3 to 250 Byte Data section, Extended Format */ + if (item_length%2 == 1) + { + /* Malformed packet */ + expert_add_info(pinfo, item, &ei_mal_io); + return; + } + + dissect_extended_format_3_to_250_byte_data(pinfo, tree, tvb, io_data_size, compute_crc, &connection_triad, packet_data); + + if (multicast) + { + dissect_extended_format_time_correction_message(tree, tvb, (io_data_size * 2) + 8); + } + } + break; + } // END case CIP_SAFETY_EXTENDED_FORMAT + } // END switch + } + else + { + /* Shouldn't happen, but at least dissect it as data */ + proto_tree_add_item(tree, hf_cipsafety_data, tvb, 0, item_length, ENC_NA); + } +} + +static int +dissect_cipsafety(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data) +{ + proto_item *ti; + proto_tree *safety_tree; + cip_safety_info_t* safety_info = (cip_safety_info_t*)data; + + /* Create display subtree for the protocol */ + ti = proto_tree_add_item(tree, proto_cipsafety, tvb, 0, -1, ENC_NA); + safety_tree = proto_item_add_subtree( ti, ett_cip_safety); + + dissect_cip_safety_data(safety_tree, ti, tvb, tvb_reported_length(tvb), pinfo, safety_info); + return tvb_captured_length(tvb); +} + +static int dissect_cipsafety_base_data(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_) +{ + cip_safety_info_t safety_info; + cip_conn_info_t eip_conn_info; + memset(&eip_conn_info, 0, sizeof(eip_conn_info)); + safety_info.eip_conn_info = &eip_conn_info; + safety_info.compute_crc = FALSE; + + // Set up parameters that will trigger dissect_cip_safety_data to parse the correct format. + safety_info.conn_type = ECIDT_T2O; + safety_info.eip_conn_info->TransportClass_trigger = 0; + safety_info.eip_conn_info->safety.format = CIP_SAFETY_BASE_FORMAT; + + return dissect_cipsafety(tvb, pinfo, tree, &safety_info); +} + +static int dissect_cipsafety_extended_data(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_) +{ + cip_safety_info_t safety_info; + cip_conn_info_t eip_conn_info; + memset(&eip_conn_info, 0, sizeof(eip_conn_info)); + safety_info.eip_conn_info = &eip_conn_info; + safety_info.compute_crc = FALSE; + + // Set up parameters that will trigger dissect_cip_safety_data to parse the correct format. + safety_info.conn_type = ECIDT_T2O; + safety_info.eip_conn_info->TransportClass_trigger = 0; + safety_info.eip_conn_info->safety.format = CIP_SAFETY_EXTENDED_FORMAT; + + return dissect_cipsafety(tvb, pinfo, tree, &safety_info); +} + +static int dissect_cipsafety_base_time_coord(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_) +{ + cip_safety_info_t safety_info; + cip_conn_info_t eip_conn_info; + memset(&eip_conn_info, 0, sizeof(eip_conn_info)); + safety_info.eip_conn_info = &eip_conn_info; + safety_info.compute_crc = FALSE; + + // Set up parameters that will trigger dissect_cip_safety_data to parse the correct format. + safety_info.conn_type = ECIDT_O2T; + safety_info.eip_conn_info->TransportClass_trigger = 0; + safety_info.eip_conn_info->safety.format = CIP_SAFETY_BASE_FORMAT; + + return dissect_cipsafety(tvb, pinfo, tree, &safety_info); +} + +static int dissect_cipsafety_extended_time_coord(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_) +{ + cip_safety_info_t safety_info; + cip_conn_info_t eip_conn_info; + memset(&eip_conn_info, 0, sizeof(eip_conn_info)); + safety_info.eip_conn_info = &eip_conn_info; + safety_info.compute_crc = FALSE; + + // Set up parameters that will trigger dissect_cip_safety_data to parse the correct format. + safety_info.conn_type = ECIDT_O2T; + safety_info.eip_conn_info->TransportClass_trigger = 0; + safety_info.eip_conn_info->safety.format = CIP_SAFETY_EXTENDED_FORMAT; + + return dissect_cipsafety(tvb, pinfo, tree, &safety_info); +} + +static int dissect_sercosiii_link_error_count_p1p2(packet_info *pinfo, proto_tree *tree, proto_item *item, tvbuff_t *tvb, + int offset, int total_len) +{ + if (total_len < 4) + { + expert_add_info(pinfo, item, &ei_mal_sercosiii_link_error_count_p1p2); + return total_len; + } + + proto_tree_add_item(tree, hf_cip_sercosiii_link_error_count_p1, tvb, offset, 2, ENC_LITTLE_ENDIAN ); + proto_tree_add_item(tree, hf_cip_sercosiii_link_error_count_p2, tvb, offset+2, 2, ENC_LITTLE_ENDIAN ); + return 4; +} + +static int dissect_sercosiii_safety_network_number(packet_info *pinfo _U_, proto_tree *tree, proto_item *item _U_, tvbuff_t *tvb, + int offset, int total_len _U_) +{ + proto_tree_add_item(tree, hf_cip_sercosiii_link_snn, tvb, offset, 6, ENC_NA); + return 6; +} + +attribute_info_t cip_safety_attribute_vals[] = { + + /* Safety Supervisor */ + {0x39, TRUE, 99, -1, "Subclass", cip_uint, &hf_cip_ssupervisor_class_subclass, NULL}, + {0x39, FALSE, 1, -1, "Number of Attributes", cip_usint, &hf_cip_ssupervisor_num_attr, NULL}, + {0x39, FALSE, 2, -1, "Attribute List", cip_usint_array, &hf_cip_ssupervisor_attr_list, NULL}, + {0x39, FALSE, 5, -1, "Manufacturer Name", cip_short_string, &hf_cip_ssupervisor_manufacture_name, NULL}, + {0x39, FALSE, 6, -1, "Manufacturer Model Number", cip_short_string, &hf_cip_ssupervisor_manufacture_model_number, NULL}, + {0x39, FALSE, 7, -1, "Software Revision Level", cip_short_string, &hf_cip_ssupervisor_sw_rev_level, NULL}, + {0x39, FALSE, 8, -1, "Hardware Revision Level", cip_short_string, &hf_cip_ssupervisor_hw_rev_level, NULL}, + {0x39, FALSE, 9, -1, "Manufacturer Serial Number", cip_short_string, &hf_cip_ssupervisor_manufacture_serial_number, NULL}, + {0x39, FALSE, 10, -1, "Device Configuration", cip_short_string, &hf_cip_ssupervisor_device_config, NULL}, + {0x39, FALSE, 11, -1, "Device Status", cip_usint, &hf_cip_ssupervisor_device_status, NULL}, + {0x39, FALSE, 12, -1, "Exception Status", cip_byte, &hf_cip_ssupervisor_exception_status, NULL}, + {0x39, FALSE, 13, -1, "Exception Detail Alarm", cip_dissector_func, NULL, dissect_s_supervisor_exception_detail_common}, + {0x39, FALSE, 14, -1, "Exception Detail Warning", cip_dissector_func, NULL, dissect_s_supervisor_exception_detail_common}, + {0x39, FALSE, 15, -1, "Alarm Enable", cip_bool, &hf_cip_ssupervisor_alarm_enable, NULL}, + {0x39, FALSE, 16, -1, "Warning Enable", cip_bool, &hf_cip_ssupervisor_warning_enable, NULL}, + {0x39, FALSE, 17, -1, "Time", cip_date_and_time, &hf_cip_ssupervisor_time, NULL}, + {0x39, FALSE, 18, -1, "Clock Power Cycle Behavior", cip_usint, &hf_cip_ssupervisor_clock_power_cycle_behavior, NULL}, + {0x39, FALSE, 19, -1, "Last Maintenance Date", cip_date, &hf_cip_ssupervisor_last_maintenance_date, NULL}, + {0x39, FALSE, 20, -1, "Next Scheduled Maintenance Date", cip_date, &hf_cip_ssupervisor_next_scheduled_maintenance_date, NULL}, + {0x39, FALSE, 21, -1, "Scheduled Maintenance Expiration Timer", cip_int, &hf_cip_ssupervisor_scheduled_maintenance_expiration_timer, NULL}, + {0x39, FALSE, 22, -1, "Scheduled Maintenance Expiration Warning Enable", cip_bool, &hf_cip_ssupervisor_scheduled_maintenance_expiration_warning_enable, NULL}, + {0x39, FALSE, 23, -1, "Run Hours", cip_udint, &hf_cip_ssupervisor_run_hours, NULL}, + {0x39, FALSE, 24, -1, "Configuration Lock", cip_bool, &hf_cip_ssupervisor_configuration_lock, NULL}, + {0x39, FALSE, 25, -1, "Configuration UNID (CFUNID)", cip_dissector_func, NULL, dissect_s_supervisor_configuration_unid}, + {0x39, FALSE, 26, -1, "Safety Configuration Identifier (SCID)", cip_dissector_func, NULL, dissect_s_supervisor_safety_configuration_id}, + {0x39, FALSE, 27, -1, "Target UNID (TUNID)", cip_dissector_func, NULL, dissect_s_supervisor_target_unid}, + {0x39, FALSE, 28, -1, "Output Connection Point Owners", cip_dissector_func, NULL, dissect_s_supervisor_output_connection_point_owners}, + {0x39, FALSE, 29, -1, "Proposed TUNID", cip_dissector_func, NULL, dissect_s_supervisor_proposed_tunid}, + {0x39, FALSE, 99, -1, "Subclass", cip_uint, &hf_cip_ssupervisor_instance_subclass, NULL}, + + /* Safety Validator */ + {0x3A, TRUE, 8, -1, "Safety Connection Fault Count", cip_uint, &hf_cip_svalidator_sconn_fault_count, NULL}, + {0x3A, FALSE, 1, 0, "Safety Validator State", cip_usint, &hf_cip_svalidator_state, NULL}, + {0x3A, FALSE, 2, 1, "Safety Validator Type", cip_dissector_func, NULL, dissect_s_validator_type}, + {0x3A, FALSE, 3, 2, "Ping Interval EPI Multiplier", cip_uint, &hf_cip_svalidator_ping_epi, NULL}, + {0x3A, FALSE, 4, 3, "Time Coord Msg Min Multiplier", cip_dissector_func, NULL, dissect_s_validator_time_coord_msg_min_mult}, + {0x3A, FALSE, 5, 4, "Network Time Expectation Multiplier", cip_dissector_func, NULL, dissect_s_validator_network_time_multiplier}, + {0x3A, FALSE, 6, 5, "Timeout Multiplier", cip_dissector_func, NULL, dissect_s_validator_timeout_multiplier}, + {0x3A, FALSE, 7, 6, "Max Consumer Number", cip_usint, &hf_cip_svalidator_max_consumer_num, NULL}, + {0x3A, FALSE, 8, 7, "Data Connection Instance", cip_uint, &hf_cip_svalidator_data_conn_inst, NULL}, + {0x3A, FALSE, 9, 8, "Coordination Connection Instance", cip_dissector_func, NULL, dissect_s_validator_coordination_conn_inst}, + {0x3A, FALSE, 10, 9, "Correction Connection Instance", cip_uint, &hf_cip_svalidator_correction_conn_inst, NULL}, + {0x3A, FALSE, 11, 10, "CCO Binding", cip_uint, &hf_cip_svalidator_cco_binding, NULL}, + {0x3A, FALSE, 12, 11, "Max Data Age", cip_uint, &hf_cip_svalidator_max_data_age, NULL}, + {0x3A, FALSE, 13, 12, "Application Data Path", cip_dissector_func, NULL, dissect_s_validator_app_data_path}, + /* Note: Get Attributes All can't get to "Error Code", because dissect_s_validator_app_data_path() will use + all remaining bytes. */ + {0x3A, FALSE, 14, 13, "Error Code", cip_uint, &hf_cip_svalidator_error_code, NULL}, + {0x3A, FALSE, 15, -1, "Producer/Consumer Fault Counters", cip_dissector_func, NULL, dissect_s_validator_prod_cons_fault_count}, + + /* SERCOS III Link */ + {0x4C, FALSE, 1, -1, "Safety Network Number", cip_dissector_func, NULL, dissect_sercosiii_safety_network_number}, + {0x4C, FALSE, 2, -1, "Communication Cycle Time", cip_udint, &hf_cip_sercosiii_link_communication_cycle_time, NULL}, + {0x4C, FALSE, 3, -1, "Interface Status", cip_word, &hf_cip_sercosiii_link_interface_status, NULL}, + {0x4C, FALSE, 4, -1, "Error counter MST-P/S", cip_uint, &hf_cip_sercosiii_link_error_count_mstps, NULL}, + {0x4C, FALSE, 5, -1, "Error counter Port1 and Port2", cip_dissector_func, NULL, dissect_sercosiii_link_error_count_p1p2}, + {0x4C, FALSE, 6, -1, "SERCOS address", cip_uint, &hf_cip_sercosiii_link_sercos_address, NULL}, +}; + +/* + * Protocol initialization + */ + +/* + * Function name: proto_register_cipsafety + * + * Purpose: Register the protocol with Wireshark, a script will add this protocol + * to the list of protocols during the build process. This function is where the + * header fields and subtree identifiers are registered. + * + * Returns: void + */ +void +proto_register_cipsafety(void) +{ + /* This is a list of header fields that can be used in the dissection or + * to use in a filter expression */ + static hf_register_info hf[] = + { + { &hf_cip_reqrsp, + { "Request/Response", "cip.rr", + FT_UINT8, BASE_HEX, VALS(cip_sc_rr), CIP_SC_RESPONSE_MASK, "Request or Response message", HFILL } + }, + { &hf_cip_data, + { "Data", "cip.data", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } + }, + + { &hf_cipsafety_data, + { "Data", "cipsafety.data", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cipsafety_mode_byte, + { "Mode Byte", "cipsafety.mode_byte", + FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cipsafety_mode_byte_ping_count, + { "Ping Count", "cipsafety.mode_byte.ping_count", + FT_UINT8, BASE_DEC, NULL, 0x03, NULL, HFILL } + }, + { &hf_cipsafety_mode_byte_not_tbd, + { "Not TBD Bit", "cipsafety.mode_byte.not_tbd", + FT_BOOLEAN, 8, NULL, 0x04, NULL, HFILL } + }, + { &hf_cipsafety_mode_byte_tbd_2_copy, + { "TBD 2 Bit Copy", "cipsafety.mode_byte.tbd_2_copy", + FT_BOOLEAN, 8, NULL, 0x08, NULL, HFILL } + }, + { &hf_cipsafety_mode_byte_not_run_idle, + { "Not Run/Idle", "cipsafety.mode_byte.not_run_idle", + FT_BOOLEAN, 8, NULL, 0x10, NULL, HFILL } + }, + { &hf_cipsafety_mode_byte_tbd, + { "TBD Bit", "cipsafety.mode_byte.tbd", + FT_BOOLEAN, 8, NULL, 0x20, NULL, HFILL } + }, + { &hf_cipsafety_mode_byte_tbd_2_bit, + { "TBD 2 Bit", "cipsafety.mode_byte.tbd_2_bit", + FT_BOOLEAN, 8, NULL, 0x40, NULL, HFILL } + }, + { &hf_cipsafety_mode_byte_run_idle, + { "Run/Idle", "cipsafety.mode_byte.run_idle", + FT_BOOLEAN, 8, NULL, 0x80, NULL, HFILL } + }, + { &hf_cipsafety_crc_s1, + { "CRC S1", "cipsafety.crc_s1", + FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cipsafety_crc_s1_status, + { "CRC S1 Status", "cipsafety.crc_s1.status", + FT_UINT8, BASE_NONE, VALS(proto_checksum_vals), 0, NULL, HFILL } + }, + { &hf_cipsafety_crc_s2, + { "CRC S2", "cipsafety.crc_s2", + FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cipsafety_crc_s2_status, + { "CRC S2 Status", "cipsafety.crc_s2.status", + FT_UINT8, BASE_NONE, VALS(proto_checksum_vals), 0, NULL, HFILL } + }, + { &hf_cipsafety_crc_s3, + { "CRC S3", "cipsafety.crc_s3", + FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cipsafety_crc_s3_status, + { "CRC S3 Status", "cipsafety.crc_s3.status", + FT_UINT8, BASE_NONE, VALS(proto_checksum_vals), 0, NULL, HFILL } + }, + { &hf_cipsafety_complement_crc_s3, + { "Complement CRC S3", "cipsafety.complement_crc_s3", + FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cipsafety_complement_crc_s3_status, + { "Complement CRC S3 Status", "cipsafety.complement_crc_s3.status", + FT_UINT8, BASE_NONE, VALS(proto_checksum_vals), 0, NULL, HFILL } + }, + { &hf_cipsafety_timestamp, + { "Timestamp", "cipsafety.timestamp", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cipsafety_ack_byte, + { "ACK Byte", "cipsafety.ack_byte", + FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cipsafety_ack_byte_ping_count_reply, + { "Ping Count Reply", "cipsafety.ack_byte.ping_count_reply", + FT_UINT8, BASE_HEX, NULL, 0x03, NULL, HFILL } + }, + { &hf_cipsafety_ack_byte_reserved1, + { "Reserved", "cipsafety.ack_byte.reserved1", + FT_UINT8, BASE_HEX, NULL, 0x04, NULL, HFILL } + }, + { &hf_cipsafety_ack_byte_ping_response, + { "Ping Response", "cipsafety.ack_byte.ping_response", + FT_BOOLEAN, 8, NULL, 0x08, NULL, HFILL } + }, + { &hf_cipsafety_ack_byte_reserved2, + { "Reserved", "cipsafety.ack_byte.reserved2", + FT_UINT8, BASE_HEX, NULL, 0x70, NULL, HFILL } + }, + { &hf_cipsafety_ack_byte_parity_even, + { "Parity Even", "cipsafety.ack_byte.parity_even", + FT_BOOLEAN, 8, NULL, 0x80, NULL, HFILL } + }, + { &hf_cipsafety_ack_byte2, + { "ACK Byte 2", "cipsafety.ack_byte2", + FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cipsafety_consumer_time_value, + { "Consumer Time Value", "cipsafety.consumer_time_value", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cipsafety_mcast_byte, + { "MCAST Byte", "cipsafety.mcast_byte", + FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cipsafety_mcast_byte_consumer_num, + { "Consumer #", "cipsafety.mcast_byte.consumer_num", + FT_UINT8, BASE_HEX, NULL, 0x0F, NULL, HFILL } + }, + { &hf_cipsafety_mcast_byte_reserved1, + { "Reserved", "cipsafety.mcast_byte.reserved1", + FT_UINT8, BASE_HEX, NULL, 0x10, NULL, HFILL } + }, + { &hf_cipsafety_mcast_byte_mai, + { "Multicast Active/Idle", "cipsafety.mcast_byte.active_idle", + FT_BOOLEAN, 8, TFS(&cip_safety_vals_active_idle), 0x20, NULL, HFILL } + }, + { &hf_cipsafety_mcast_byte_reserved2, + { "Reserved", "cipsafety.mcast_byte.reserved2", + FT_UINT8, BASE_HEX, NULL, 0x40, NULL, HFILL } + }, + { &hf_cipsafety_mcast_byte_parity_even, + { "Parity Even", "cipsafety.mcast_byte.parity_even", + FT_BOOLEAN, 8, NULL, 0x80, NULL, HFILL } + }, + { &hf_cipsafety_mcast_byte2, + { "MCAST Byte 2", "cipsafety.mcast_byte2", + FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cipsafety_time_correction, + { "Time Correction", "cipsafety.time_correction", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cipsafety_crc_s5_0, + { "CRC S5_0", "cipsafety.crc_s5_0", + FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cipsafety_crc_s5_1, + { "CRC S5_1", "cipsafety.crc_s5_1", + FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cipsafety_crc_s5_2, + { "CRC S5_2", "cipsafety.crc_s5_2", + FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cipsafety_crc_s5_status, + { "CRC S5 Status", "cipsafety.crc_s5.status", + FT_UINT8, BASE_NONE, VALS(proto_checksum_vals), 0, NULL, HFILL } + }, + { &hf_cipsafety_complement_data, + { "Complement Data", "cipsafety.complement_data", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } + }, + + { &hf_cip_sercosiii_link_snn, + { "Data", "cipsafety.sercosiii_link.snn", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_sercosiii_link_communication_cycle_time, + { "Communication Cycle Time", "cipsafety.sercosiii_link.communication_cycle_time", + FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_sercosiii_link_interface_status, + { "Interface Status", "cipsafety.sercosiii_link.interface_status", + FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_sercosiii_link_error_count_mstps, + { "Error Counter MST-P/S", "cipsafety.sercosiii_link.error_count_mstps", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_sercosiii_link_error_count_p1, + { "Error Count Port 1", "cipsafety.sercosiii_link.error_count_p1", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_sercosiii_link_error_count_p2, + { "Error Count Port 2", "cipsafety.sercosiii_link.error_count_p2", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_sercosiii_link_sercos_address, + { "SERCOS Address", "cipsafety.sercosiii_link.sercos_address", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } + }, + }; + + static hf_register_info hf_ssupervisor[] = { + { &hf_cip_ssupervisor_sc, + { "Service", "cipsafety.ssupervisor.sc", + FT_UINT8, BASE_HEX, VALS(cip_sc_vals_ssupervisor), CIP_SC_MASK, NULL, HFILL } + }, + { &hf_cip_ssupervisor_recover_data, + { "Data", "cipsafety.ssupervisor.recover.data", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_perform_diag_data, + { "Data", "cipsafety.ssupervisor.perform_diag.data", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_configure_request_password, + { "Password", "cipsafety.ssupervisor.configure_request.password", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_configure_request_tunid, + { "Target UNID", "cipsafety.ssupervisor.configure_request.tunid", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_configure_request_tunid_snn_timestamp, + { "TUNID SNN Timestamp", "cipsafety.ssupervisor.configure_request.tunid.snn.timestamp", + FT_ABSOLUTE_TIME, ABSOLUTE_TIME_UTC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_configure_request_tunid_snn_date, + { "TUNID SNN (Manual) Date", "cipsafety.ssupervisor.configure_request.tunid.snn.date", + FT_UINT16, BASE_HEX, VALS(cipsafety_snn_date_vals), 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_configure_request_tunid_snn_time, + { "TUNID SNN (Manual) Time", "cipsafety.ssupervisor.configure_request.tunid.snn.time", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_configure_request_tunid_nodeid, + { "Node ID", "cipsafety.ssupervisor.configure_request.tunid.nodeid", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_configure_request_ounid, + { "Originator UNID", "cipsafety.ssupervisor.configure_request.ounid", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_configure_request_ounid_snn_timestamp, + { "OUNID SNN Timestamp", "cipsafety.ssupervisor.configure_request.ounid.snn.timestamp", + FT_ABSOLUTE_TIME, ABSOLUTE_TIME_UTC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_configure_request_ounid_snn_date, + { "OUNID SNN (Manual) Date", "cipsafety.ssupervisor.configure_request.ounid.snn.date", + FT_UINT16, BASE_HEX, VALS(cipsafety_snn_date_vals), 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_configure_request_ounid_snn_time, + { "OUNID SNN (Manual) Time", "cipsafety.ssupervisor.configure_request.ounid.snn.time", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_configure_request_ounid_nodeid, + { "Node ID", "cipsafety.ssupervisor.configure_request.ounid.nodeid", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_validate_configuration_sccrc, + { "SCCRC", "cipsafety.ssupervisor.validate_configuration.sccrc", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_validate_configuration_scts_timestamp, + { "SCTS (Timestamp)", "cipsafety.ssupervisor.validate_configuration.scts.timestamp", + FT_ABSOLUTE_TIME, ABSOLUTE_TIME_UTC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_validate_configuration_scts_date, + { "SCTS (Manual) Date", "cipsafety.ssupervisor.validate_configuration.scts.date", + FT_UINT16, BASE_HEX, VALS(cipsafety_snn_date_vals), 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_validate_configuration_scts_time, + { "SCTS (Manual) Time", "cipsafety.ssupervisor.validate_configuration.scts.time", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_validate_configuration_ext_error, + { "Extended Error", "cipsafety.ssupervisor.validate_configuration.ext_error", + FT_UINT16, BASE_DEC, VALS(cip_ssupervisor_validate_configuration_ext_error_vals), 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_set_password_current_password, + { "Current Password", "cipsafety.ssupervisor.set_password.current_pass", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_set_password_new_password, + { "New Password", "cipsafety.ssupervisor.set_password.new_pass", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_configure_lock_value, + { "Lock Value", "cipsafety.ssupervisor.configure_lock.lock", + FT_UINT8, BASE_DEC, VALS(cip_ssupervisor_lock_vals), 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_configure_lock_password, + { "Password", "cipsafety.ssupervisor.configure_lock.password", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_configure_lock_tunid, + { "Target UNID", "cipsafety.ssupervisor.configure_lock.tunid", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_configure_lock_tunid_snn_timestamp, + { "TUNID SNN Timestamp", "cipsafety.ssupervisor.configure_lock.tunid.snn.timestamp", + FT_ABSOLUTE_TIME, ABSOLUTE_TIME_UTC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_configure_lock_tunid_snn_date, + { "TUNID SNN (Manual) Date", "cipsafety.ssupervisor.configure_lock.tunid.snn.date", + FT_UINT16, BASE_HEX, VALS(cipsafety_snn_date_vals), 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_configure_lock_tunid_snn_time, + { "TUNID SNN (Manual) Time", "cipsafety.ssupervisor.configure_lock.tunid.snn.time", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_configure_lock_tunid_nodeid, + { "Node ID", "cipsafety.ssupervisor.configure_lock.tunid.nodeid", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_mode_change_value, + { "Value", "cipsafety.ssupervisor.mode_change.value", + FT_UINT8, BASE_DEC, VALS(cip_ssupervisor_change_mode_vals), 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_mode_change_password, + { "Password", "cipsafety.ssupervisor.mode_change.password", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_reset_type, + { "Reset Type", "cipsafety.ssupervisor.reset.type", + FT_UINT8, BASE_DEC, VALS(cip_reset_type_vals), 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_reset_password, + { "Password", "cipsafety.ssupervisor.reset.password", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_reset_tunid, + { "Target UNID", "cipsafety.ssupervisor.reset.tunid", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_reset_tunid_tunid_snn_timestamp, + { "TUNID SNN Timestamp", "cipsafety.ssupervisor.reset.tunid.snn.timestamp", + FT_ABSOLUTE_TIME, ABSOLUTE_TIME_UTC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_reset_tunid_tunid_snn_date, + { "TUNID SNN (Manual) Date", "cipsafety.ssupervisor.reset.tunid.snn.date", + FT_UINT16, BASE_HEX, VALS(cipsafety_snn_date_vals), 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_reset_tunid_tunid_snn_time, + { "TUNID SNN (Manual) Time", "cipsafety.ssupervisor.reset.tunid.snn.time", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_reset_tunid_nodeid, + { "Node ID", "cipsafety.ssupervisor.reset.tunid.nodeid", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_reset_attr_bitmap, + { "Attribute Bit Map", "cipsafety.ssupervisor.reset.attr_bitmap", + FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_reset_attr_bitmap_macid, + { "Preserve MacID", "cipsafety.ssupervisor.reset.attr_bitmap.macid", + FT_BOOLEAN, 8, NULL, 0x01, NULL, HFILL } + }, + { &hf_cip_ssupervisor_reset_attr_bitmap_baudrate, + { "Preserve Baud Rate", "cipsafety.ssupervisor.reset.attr_bitmap.baudrate", + FT_BOOLEAN, 8, NULL, 0x02, NULL, HFILL } + }, + { &hf_cip_ssupervisor_reset_attr_bitmap_tunid, + { "Preserve TUNID", "cipsafety.ssupervisor.reset.attr_bitmap.tunid", + FT_BOOLEAN, 8, NULL, 0x04, NULL, HFILL } + }, + { &hf_cip_ssupervisor_reset_attr_bitmap_password, + { "Preserve Password", "cipsafety.ssupervisor.reset.attr_bitmap.password", + FT_BOOLEAN, 8, NULL, 0x08, NULL, HFILL } + }, + { &hf_cip_ssupervisor_reset_attr_bitmap_cfunid, + { "Preserve CFUNID", "cipsafety.ssupervisor.reset.attr_bitmap.cfunid", + FT_BOOLEAN, 8, NULL, 0x10, NULL, HFILL } + }, + { &hf_cip_ssupervisor_reset_attr_bitmap_ocpunid, + { "Preserve OPCUNID", "cipsafety.ssupervisor.reset.attr_bitmap.ocpunid", + FT_BOOLEAN, 8, NULL, 0x20, NULL, HFILL } + }, + { &hf_cip_ssupervisor_reset_attr_bitmap_reserved, + { "Reserved", "cipsafety.ssupervisor.reset.attr_bitmap.reserved", + FT_BOOLEAN, 8, NULL, 0x40, NULL, HFILL } + }, + { &hf_cip_ssupervisor_reset_attr_bitmap_extended, + { "Use Extended Map", "cipsafety.ssupervisor.reset.attr_bitmap.extended", + FT_BOOLEAN, 8, NULL, 0x80, NULL, HFILL } + }, + { &hf_cip_ssupervisor_reset_password_data_size, + { "Data Size", "cipsafety.ssupervisor.reset_password.data_size", + FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_reset_password_data, + { "Password Data", "cipsafety.ssupervisor.reset_password.password_data", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_propose_tunid_tunid, + { "Target UNID", "cipsafety.ssupervisor.propose_tunid.tunid", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_propose_tunid_tunid_snn_timestamp, + { "TUNID SNN Timestamp", "cipsafety.ssupervisor.propose_tunid.tunid.snn.timestamp", + FT_ABSOLUTE_TIME, ABSOLUTE_TIME_UTC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_propose_tunid_tunid_snn_date, + { "TUNID SNN (Manual) Date", "cipsafety.ssupervisor.propose_tunid.tunid.snn.date", + FT_UINT16, BASE_HEX, VALS(cipsafety_snn_date_vals), 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_propose_tunid_tunid_snn_time, + { "TUNID SNN (Manual) Time", "cipsafety.ssupervisor.propose_tunid.tunid.snn.time", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_propose_tunid_tunid_nodeid, + { "Node ID", "cipsafety.ssupervisor.propose_tunid.tunid.nodeid", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_apply_tunid_tunid, + { "Target UNID", "cipsafety.ssupervisor.apply_tunid.tunid", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_apply_tunid_tunid_snn_timestamp, + { "TUNID SNN Timestamp", "cipsafety.ssupervisor.apply_tunid.tunid.snn.timestamp", + FT_ABSOLUTE_TIME, ABSOLUTE_TIME_UTC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_apply_tunid_tunid_snn_date, + { "TUNID SNN (Manual) Date", "cipsafety.ssupervisor.apply_tunid.tunid.snn.date", + FT_UINT16, BASE_HEX, VALS(cipsafety_snn_date_vals), 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_apply_tunid_tunid_snn_time, + { "TUNID SNN (Manual) Time", "cipsafety.ssupervisor.apply_tunid.tunid.snn.time", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_apply_tunid_tunid_nodeid, + { "Node ID", "cipsafety.ssupervisor.apply_tunid.tunid.nodeid", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_class_subclass, + { "Subclass", "cipsafety.ssupervisor.class_subclass", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_num_attr, + { "Number of Attributes", "cipsafety.ssupervisor.num_attr", + FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_attr_list, + { "Attributes List Item", "cipsafety.ssupervisor.attr_item", + FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_manufacture_name, + { "Manufacturer Name", "cipsafety.ssupervisor.manufacture_name", + FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_manufacture_model_number, + { "Manufacturer Model Number", "cipsafety.ssupervisor.manufacture_model_number", + FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_sw_rev_level, + { "Software Revision Level", "cipsafety.ssupervisor.sw_rev_level", + FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_hw_rev_level, + { "Hardware Revision Level", "cipsafety.ssupervisor.hw_rev_level", + FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_manufacture_serial_number, + { "Manufacturer Serial Number", "cipsafety.ssupervisor.manufacture_serial_number", + FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_device_config, + { "Device Configuration", "cipsafety.ssupervisor.device_config", + FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_device_status, + { "Device Status", "cipsafety.ssupervisor.device_status", + FT_UINT8, BASE_DEC, VALS(cip_ssupervisor_device_status_type_vals), 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_exception_status, + { "Exception Status", "cipsafety.ssupervisor.exception_status", + FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_exception_detail_ced_size, + { "Common Exception Detail Size", "cipsafety.ssupervisor.exception_detail.ced.size", + FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_exception_detail_ced_detail, + { "Common Exception Detail Data", "cipsafety.ssupervisor.exception_detail.ced.detail", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_exception_detail_ded_size, + { "Device Exception Detail Size", "cipsafety.ssupervisor.exception_detail.ded.size", + FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_exception_detail_ded_detail, + { "Device Exception Detail Data", "cipsafety.ssupervisor.exception_detail.ded.detail", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_exception_detail_med_size, + { "Manufacturer Exception Detail Size", "cipsafety.ssupervisor.exception_detail.med.size", + FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_exception_detail_med_detail, + { "Manufacturer Exception Detail Data", "cipsafety.ssupervisor.exception_detail.med.detail", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_alarm_enable, + { "Exception Detail Alarm", "cipsafety.ssupervisor.alarm_enable", + FT_BOOLEAN, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_warning_enable, + { "Exception Detail Warning", "cipsafety.ssupervisor.warning_enable", + FT_BOOLEAN, BASE_NONE, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_time, + { "Time", "cipsafety.ssupervisor.time", + FT_ABSOLUTE_TIME, ABSOLUTE_TIME_UTC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_clock_power_cycle_behavior, + { "Clock Power Cycle Behavior", "cipsafety.ssupervisor.clock_power_cycle_behavior", + FT_UINT8, BASE_DEC, VALS(cip_ssupervisor_clock_power_cycle_type_vals), 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_last_maintenance_date, + { "Last Maintenance Date", "cipsafety.ssupervisor.last_maintenance_date", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_next_scheduled_maintenance_date, + { "Next Scheduled Maintenance Date", "cipsafety.ssupervisor.next_scheduled_maintenance_date", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_scheduled_maintenance_expiration_timer, + { "Scheduled Maintenance Expiration Timer", "cipsafety.ssupervisor.scheduled_maintenance_expiration_timer", + FT_INT16, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_scheduled_maintenance_expiration_warning_enable, + { "Scheduled Maintenance Expiration Warning Enable", "cipsafety.ssupervisor.scheduled_maintenance_expiration_warning", + FT_BOOLEAN, BASE_NONE, TFS(&tfs_enabled_disabled), 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_run_hours, + { "Run Hours", "cipsafety.ssupervisor.run_hours", + FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_configuration_lock, + { "Configuration Lock", "cipsafety.ssupervisor.configuration_lock", + FT_UINT8, BASE_DEC, VALS(cip_ssupervisor_lock_vals), 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_configuration_unid_snn_timestamp, + { "Configuration UNID SNN Timestamp", "cipsafety.ssupervisor.configuration_unid.snn.timestamp", + FT_ABSOLUTE_TIME, ABSOLUTE_TIME_UTC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_configuration_unid_snn_date, + { "Configuration UNID SNN (Manual) Date", "cipsafety.ssupervisor.configuration_unid.snn.date", + FT_UINT16, BASE_HEX, VALS(cipsafety_snn_date_vals), 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_configuration_unid_snn_time, + { "Configuration UNID SNN (Manual) Time", "cipsafety.ssupervisor.configuration_unid.snn.time", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_configuration_unid_nodeid, + { "Configuration UNID Node ID", "cipsafety.ssupervisor.configuration_unid.nodeid", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_safety_configuration_id_snn_timestamp, + { "Safety Configuration ID SNN Timestamp", "cipsafety.ssupervisor.safety_configuration_id.snn.timestamp", + FT_ABSOLUTE_TIME, ABSOLUTE_TIME_UTC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_safety_configuration_id_snn_date, + { "Safety Configuration ID SNN (Manual) Date", "cipsafety.ssupervisor.safety_configuration_id.snn.date", + FT_UINT16, BASE_HEX, VALS(cipsafety_snn_date_vals), 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_safety_configuration_id_snn_time, + { "Safety Configuration ID SNN (Manual) Time", "cipsafety.ssupervisor.safety_configuration_id.snn.time", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_safety_configuration_id_sccrc, + { "Safety Configuration ID SCCRC", "cipsafety.ssupervisor.safety_configuration_id.sccrc", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_target_unid_snn_timestamp, + { "Target UNID SNN Timestamp", "cipsafety.ssupervisor.target_unid.snn.timestamp", + FT_ABSOLUTE_TIME, ABSOLUTE_TIME_UTC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_target_unid_snn_date, + { "Target UNID SNN (Manual) Date", "cipsafety.ssupervisor.target_unid.snn.date", + FT_UINT16, BASE_HEX, VALS(cipsafety_snn_date_vals), 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_target_unid_snn_time, + { "Target UNID SNN (Manual) Time", "cipsafety.ssupervisor.target_unid.snn.time", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_target_unid_nodeid, + { "Target UNID Node ID", "cipsafety.ssupervisor.target_unid.nodeid", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_cp_owners_num_entries, + { "Number of Array Entries", "cipsafety.ssupervisor.cp_owners.num_entries", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_output_cp_owners_ocpunid_snn_timestamp, + { "OCPUNID SNN Timestamp", "cipsafety.ssupervisor.cp_owners.snn.timestamp", + FT_ABSOLUTE_TIME, ABSOLUTE_TIME_UTC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_output_cp_owners_ocpunid_snn_date, + { "OCPUNID SNN (Manual) Date", "cipsafety.ssupervisor.cp_owners.snn.date", + FT_UINT16, BASE_HEX, VALS(cipsafety_snn_date_vals), 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_output_cp_owners_ocpunid_snn_time, + { "OCPUNID SNN (Manual) Time", "cipsafety.ssupervisor.cp_owners.snn.time", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_output_cp_owners_ocpunid_nodeid, + { "OCPUNID Node ID", "cipsafety.ssupervisor.cp_owners.ocpunid.nodeid", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_cp_owners_app_path_size, + { "EPATH Size", "cipsafety.ssupervisor.cp_owners.epath_size", + FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_proposed_tunid_snn_timestamp, + { "Proposed TUNID SNN Timestamp", "cipsafety.ssupervisor.proposed_tunid.snn.timestamp", + FT_ABSOLUTE_TIME, ABSOLUTE_TIME_UTC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_proposed_tunid_snn_date, + { "Proposed TUNID SNN (Manual) Date", "cipsafety.ssupervisor.proposed_tunid.snn.date", + FT_UINT16, BASE_HEX, VALS(cipsafety_snn_date_vals), 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_proposed_tunid_snn_time, + { "Proposed TUNID SNN (Manual) Time", "cipsafety.ssupervisor.proposed_tunid.snn.time", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_proposed_tunid_nodeid, + { "Proposed TUNID Node ID", "cipsafety.ssupervisor.proposed_tunid.nodeid", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_ssupervisor_instance_subclass, + { "Subclass", "cipsafety.ssupervisor.instance_subclass", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } + } + }; + + static hf_register_info hf_svalidator[] = { + { &hf_cip_svalidator_sc, + { "Service", "cipsafety.svalidator.sc", + FT_UINT8, BASE_HEX, VALS(cip_sc_vals_svalidator), CIP_SC_MASK, NULL, HFILL } + }, + + { &hf_cip_svalidator_sconn_fault_count, + { "Safety Connection Fault Count", "cipsafety.svalidator.sconn_fault_count", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_svalidator_state, + { "Safety Validator State", "cipsafety.svalidator.state", + FT_UINT8, BASE_DEC, VALS(cip_svalidator_state_vals), 0, NULL, HFILL } + }, + { &hf_cip_svalidator_type, + { "Safety Validator Type", "cipsafety.svalidator.type", + FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } + }, + { &hf_cip_svalidator_type_pc, + { "Producer/Consumer", "cipsafety.svalidator.type.pc", + FT_UINT8, BASE_HEX, VALS(cip_svalidator_type_pc_vals), 0x80, NULL, HFILL } + }, + { &hf_cip_svalidator_type_conn_type, + { "Safety Connection Type", "cipsafety.svalidator.type.conn_type", + FT_UINT8, BASE_DEC, VALS(cip_svalidator_type_conn_type_vals), 0x7F, NULL, HFILL } + }, + { &hf_cip_svalidator_ping_epi, + { "Ping Interval EPI Multiplier", "cipsafety.svalidator.ping_epi", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_svalidator_time_coord_msg_min_mult_size, + { "Time Coord Msg Min Multiplier Array Size", "cipsafety.svalidator.time_coord_msg_min_mult.size", + FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_svalidator_time_coord_msg_min_mult_item, + { "Time Coord Msg Min Multiplier", "cipsafety.svalidator.time_coord_msg_min_mult.item", + FT_UINT16, BASE_CUSTOM, CF_FUNC(cip_safety_128us_fmt), 0, NULL, HFILL } + }, + { &hf_cip_svalidator_network_time_multiplier_size, + { "Network Time Expectation Multiplier Array Size", "cipsafety.svalidator.network_time_multiplier.size", + FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_svalidator_network_time_multiplier_item, + { "Network Time Expectation Multiplier", "cipsafety.svalidator.network_time_multiplier.item", + FT_UINT16, BASE_CUSTOM, CF_FUNC(cip_safety_128us_fmt), 0, NULL, HFILL } + }, + { &hf_cip_svalidator_timeout_multiplier_size, + { "Timeout Multiplier Array Size", "cipsafety.svalidator.timeout_multiplier.size", + FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_svalidator_timeout_multiplier_item, + { "Timeout Multiplier", "cipsafety.svalidator.timeout_multiplier.item", + FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_svalidator_max_consumer_num, + { "Max Consumer Number", "cipsafety.svalidator.max_consumer_num", + FT_UINT8, BASE_DEC|BASE_RANGE_STRING, RVALS(safety_max_consumer_numbers), 0, NULL, HFILL } + }, + { &hf_cip_svalidator_data_conn_inst, + { "Data Connection Instance", "cipsafety.svalidator.data_conn_inst", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_svalidator_coordination_conn_inst_size, + { "Coordination Connection Instance Size", "cipsafety.svalidator.coordination_conn_inst.size", + FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_svalidator_coordination_conn_inst_item, + { "Coordination Connection Instance Item", "cipsafety.svalidator.coordination_conn_inst.item", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_svalidator_correction_conn_inst, + { "Correction Connection Instance", "cipsafety.svalidator.correction_conn_inst", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_svalidator_cco_binding, + { "CCO Binding", "cipsafety.svalidator.cco_binding", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_svalidator_max_data_age, + { "Max Data Age", "cipsafety.svalidator.max_data_age", + FT_UINT16, BASE_CUSTOM, CF_FUNC(cip_safety_128us_fmt), 0, NULL, HFILL } + }, + { &hf_cip_svalidator_error_code, + { "Error Code", "cipsafety.svalidator.error_code", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_svalidator_prod_cons_fault_count_size, + { "Producer/Consumer Counter Array Size", "cipsafety.svalidator.prod_cons_fault_count.size", + FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } + }, + { &hf_cip_svalidator_prod_cons_fault_count_item, + { "Producer/Consumer Fault Counter", "cipsafety.svalidator.prod_cons_fault_count.item", + FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } + } + }; + + static gint *ett[] = { + &ett_cip_safety, + &ett_path, + &ett_cipsafety_mode_byte, + &ett_cipsafety_ack_byte, + &ett_cipsafety_mcast_byte + }; + + static gint *ett_ssupervisor[] = { + &ett_cip_class_s_supervisor, + &ett_ssupervisor_rrsc, + &ett_ssupervisor_cmd_data, + &ett_ssupervisor_propose_tunid, + &ett_ssupervisor_propose_tunid_snn, + &ett_ssupervisor_configure_request_tunid, + &ett_ssupervisor_configure_request_tunid_snn, + &ett_ssupervisor_configure_request_ounid, + &ett_ssupervisor_configure_request_ounid_snn, + &ett_ssupervisor_configure_lock_tunid, + &ett_ssupervisor_configure_lock_tunid_snn, + &ett_ssupervisor_reset_tunid, + &ett_ssupervisor_reset_tunid_snn, + &ett_ssupervisor_apply_tunid, + &ett_ssupervisor_apply_tunid_snn, + &ett_exception_detail_common, + &ett_exception_detail_device, + &ett_exception_detail_manufacturer, + &ett_ssupervisor_configuration_unid, + &ett_ssupervisor_configuration_unid_snn, + &ett_ssupervisor_target_unid, + &ett_ssupervisor_target_unid_snn, + &ett_ssupervisor_output_cp_owners, + &ett_ssupervisor_output_cp_owners_ocpunid, + &ett_ssupervisor_output_cp_owners_ocpunid_snn, + &ett_ssupervisor_proposed_tunid, + &ett_ssupervisor_proposed_tunid_snn, + &ett_cip_ssupervisor_reset_attr_bitmap + }; + + static gint *ett_svalidator[] = { + &ett_cip_class_s_validator, + &ett_svalidator_rrsc, + &ett_svalidator_cmd_data, + &ett_svalidator_type + }; + + static ei_register_info ei[] = { + { &ei_cipsafety_tbd_not_complemented, { "cipsafety.tbd_not_complemented", PI_PROTOCOL, PI_ERROR, "TBD bit not complemented", EXPFILL }}, + { &ei_cipsafety_tbd2_not_copied, { "cipsafety.tbd2_not_copied", PI_PROTOCOL, PI_ERROR, "TBD2 bit not copied", EXPFILL }}, + { &ei_cipsafety_run_idle_not_complemented, { "cipsafety.run_idle_not_complemented", PI_PROTOCOL, PI_ERROR, "Run/Idle bit not complemented", EXPFILL }}, + { &ei_mal_io, { "cipsafety.malformed.io", PI_MALFORMED, PI_ERROR, "Malformed CIP Safety I/O packet", EXPFILL }}, + { &ei_mal_sercosiii_link_error_count_p1p2, { "cipsafety.malformed.sercosiii_link.error_count_p1p2", PI_MALFORMED, PI_ERROR, "Malformed SERCOS III Attribute 5", EXPFILL }}, + { &ei_cipsafety_not_complement_data, { "cipsafety.not_complement_data", PI_PROTOCOL, PI_ERROR, "Data not complemented", EXPFILL }}, + { &ei_cipsafety_crc_s1, { "cipsafety.crc_s1.incorrect", PI_PROTOCOL, PI_ERROR, "CRC-S1 incorrect", EXPFILL }}, + { &ei_cipsafety_crc_s2, { "cipsafety.crc_s2.incorrect", PI_PROTOCOL, PI_ERROR, "CRC-S2 incorrect", EXPFILL }}, + { &ei_cipsafety_crc_s3, { "cipsafety.crc_s3.incorrect", PI_PROTOCOL, PI_ERROR, "CRC-S3 incorrect", EXPFILL }}, + { &ei_cipsafety_complement_crc_s3, { "cipsafety.complement_crc_s3.incorrect", PI_PROTOCOL, PI_ERROR, "Complement CRC-S3 incorrect", EXPFILL }}, + { &ei_cipsafety_crc_s5, { "cipsafety.crc_s5.incorrect", PI_PROTOCOL, PI_ERROR, "CRC-S5 incorrect", EXPFILL }}, + }; + + static ei_register_info ei_ssupervisor[] = { + { &ei_mal_ssupervisor_exception_detail_ced, { "cipsafety.ssupervisor.malformed.exception_detail.ced", PI_MALFORMED, PI_ERROR, + "Malformed Safety Supervisor Exception Detail (Common Exception Detail)", EXPFILL }}, + { &ei_mal_ssupervisor_exception_detail_ded, { "cipsafety.ssupervisor.malformed.exception_detail.ded", PI_MALFORMED, PI_ERROR, + "Malformed Safety Supervisor Exception Detail (Device Exception Detail)", EXPFILL }}, + { &ei_mal_ssupervisor_exception_detail_med, { "cipsafety.ssupervisor.malformed.exception_detail.med", PI_MALFORMED, PI_ERROR, + "Malformed Safety Supervisor Exception Detail (Manufacturer Exception Detail)", EXPFILL }}, + { &ei_mal_ssupervisor_configuration_unid, { "cipsafety.ssupervisor.malformed.configuration_unid", PI_MALFORMED, PI_ERROR, + "Malformed Safety Supervisor Configuration UNID", EXPFILL }}, + { &ei_mal_ssupervisor_safety_configuration_id, { "cipsafety.ssupervisor.malformed.safety_configuration_id", PI_MALFORMED, PI_ERROR, + "Malformed Safety Supervisor Safety Configuration Identifier", EXPFILL }}, + { &ei_mal_ssupervisor_target_unid, { "cipsafety.ssupervisor.malformed.target_unid", PI_MALFORMED, PI_ERROR, + "Malformed Safety Supervisor Target UNID", EXPFILL }}, + { &ei_mal_ssupervisor_cp_owners, { "cipsafety.ssupervisor.malformed.cp_owners", PI_MALFORMED, PI_ERROR, + "Malformed Safety Supervisor Output Connection Point Owners", EXPFILL }}, + { &ei_mal_ssupervisor_cp_owners_entry, { "cipsafety.ssupervisor.malformed.cp_owners.entry", PI_MALFORMED, PI_ERROR, + "Malformed Safety Supervisor Output Connection Point Owners (UNID)", EXPFILL }}, + { &ei_mal_ssupervisor_cp_owners_app_path_size, { "cipsafety.ssupervisor.malformed.cp_owners.app_path_size", PI_MALFORMED, PI_ERROR, + "Malformed Safety Supervisor Output Connection Point Owners (EPATH)", EXPFILL }}, + { &ei_mal_ssupervisor_proposed_tunid, { "cipsafety.ssupervisor.malformed.proposed_tunid", PI_MALFORMED, PI_ERROR, + "Malformed Safety Supervisor Proposed TUNID", EXPFILL }}, + { &ei_info_ssupervisor_tunid_cancel, { "cipsafety.ssupervisor.info.cancel_propose_apply", PI_PROTOCOL, PI_WARN, + "Cancel Proposed/Apply Operation", EXPFILL } }, + }; + + static ei_register_info ei_svalidator[] = { + { &ei_mal_svalidator_type, { "cipsafety.ssupervisor.malformed.svalidator.type", PI_MALFORMED, PI_ERROR, + "Malformed Safety Validator Type", EXPFILL }}, + { &ei_mal_svalidator_time_coord_msg_min_mult, { "cipsafety.ssupervisor.malformed.svalidator.time_coord_msg_min_mult", PI_MALFORMED, PI_ERROR, + "Malformed Safety Validator Time Coord Msg Min Multiplier", EXPFILL }}, + { &ei_mal_svalidator_network_time_multiplier, { "cipsafety.ssupervisor.malformed.svalidator.network_time_multiplier", PI_MALFORMED, PI_ERROR, + "Malformed Safety Validator Network Time Expectation Multiplier", EXPFILL }}, + { &ei_mal_svalidator_timeout_multiplier, { "cipsafety.ssupervisor.malformed.svalidator.timeout_multiplier", PI_MALFORMED, PI_ERROR, + "Malformed Safety Validator Timeout Multiplier", EXPFILL }}, + { &ei_mal_svalidator_coordination_conn_inst, { "cipsafety.ssupervisor.malformed.svalidator.coordination_conn_inst", PI_MALFORMED, PI_ERROR, + "Malformed Safety Validator Coordination Connection Instance", EXPFILL }}, + { &ei_mal_svalidator_prod_cons_fault_count, { "cipsafety.ssupervisor.malformed.svalidator.prod_cons_fault_count", PI_MALFORMED, PI_ERROR, + "Malformed Safety Validator Produce/Consume Fault Counters", EXPFILL }}, + }; + + expert_module_t* expert_cip_safety; + expert_module_t* expert_cip_class_s_supervisor; + expert_module_t* expert_cip_class_s_validator; + + /* Create a CIP Safety protocol handle */ + proto_cipsafety = proto_register_protocol("Common Industrial Protocol, Safety", "CIP Safety", "cipsafety"); + proto_register_field_array(proto_cipsafety, hf, array_length(hf)); + proto_register_subtree_array(ett, array_length(ett)); + expert_cip_safety = expert_register_protocol(proto_cipsafety); + expert_register_field_array(expert_cip_safety, ei, array_length(ei)); + + cipsafety_handle = register_dissector( "cipsafety", dissect_cipsafety, proto_cipsafety); + + // Register different protocols for "Decode As". + proto_cipsafety_base_data = proto_register_protocol_in_name_only("Common Industrial Protocol, Safety - Base - Data", + "CIP Safety - Base - Data", + "cipsafety_bd", + proto_cipsafety, + FT_PROTOCOL); + cipsafety_base_data_handle = register_dissector("cipsafety_bd", dissect_cipsafety_base_data, proto_cipsafety_base_data); + + proto_cipsafety_extended_data = proto_register_protocol_in_name_only("Common Industrial Protocol, Safety - Extended - Data", + "CIP Safety - Extended - Data", + "cipsafety_ed", + proto_cipsafety, + FT_PROTOCOL); + cipsafety_extended_data_handle = register_dissector("cipsafety_ed", dissect_cipsafety_extended_data, proto_cipsafety_extended_data); + + proto_cipsafety_base_time_coord = proto_register_protocol_in_name_only("Common Industrial Protocol, Safety - Base - Time Coordination", + "CIP Safety - Base - Time Coordination", + "cipsafety_bt", + proto_cipsafety, + FT_PROTOCOL); + cipsafety_base_time_coord_handle = register_dissector("cipsafety_bt", dissect_cipsafety_base_time_coord, proto_cipsafety_base_time_coord); + + proto_cipsafety_extended_time_coord = proto_register_protocol_in_name_only("Common Industrial Protocol, Safety - Extended - Time Coordination", + "CIP Safety - Extended - Time Coordination", + "cipsafety_et", + proto_cipsafety, + FT_PROTOCOL); + cipsafety_extended_time_coord_handle = register_dissector("cipsafety_et", dissect_cipsafety_extended_time_coord, proto_cipsafety_extended_time_coord); + + + /* Register CIP Safety objects */ + proto_cip_class_s_supervisor = proto_register_protocol("CIP Safety Supervisor", + "CIPSSupervisor", "cipssupervisor"); + cip_class_s_supervisor_handle = register_dissector("cipssupervisor", dissect_cip_class_s_supervisor, proto_cip_class_s_supervisor ); + proto_register_field_array(proto_cip_class_s_supervisor, hf_ssupervisor, array_length(hf_ssupervisor)); + proto_register_subtree_array(ett_ssupervisor, array_length(ett_ssupervisor)); + expert_cip_class_s_supervisor = expert_register_protocol(proto_cip_class_s_supervisor); + expert_register_field_array(expert_cip_class_s_supervisor, ei_ssupervisor, array_length(ei_ssupervisor)); + + proto_cip_class_s_validator = proto_register_protocol("CIP Safety Validator", + "CIPSValidator", "cipsvalidator"); + cip_class_s_validator_handle = register_dissector("cipsvalidator", dissect_cip_class_s_validator, proto_cip_class_s_validator ); + proto_register_field_array(proto_cip_class_s_validator, hf_svalidator, array_length(hf_svalidator)); + proto_register_subtree_array(ett_svalidator, array_length(ett_svalidator)); + expert_cip_class_s_validator = expert_register_protocol(proto_cip_class_s_validator); + expert_register_field_array(expert_cip_class_s_validator, ei_svalidator, array_length(ei_svalidator)); +} + +/* + * Function name: proto_reg_handoff_cipsafety + * + * Purpose: This function will setup the automatic dissection of the CIP Safety datagram, + * it is called by Wireshark when the protocol is registered + * + * Returns: void + */ +void +proto_reg_handoff_cipsafety(void) +{ + /* Register dissector handle for Safety Supervisor */ + dissector_add_uint( "cip.class.iface", CI_CLS_SAFETY_SUPERVISOR, cip_class_s_supervisor_handle ); + + /* Register dissector handle for Safety Validator */ + dissector_add_uint( "cip.class.iface", CI_CLS_SAFETY_VALIDATOR, cip_class_s_validator_handle ); + heur_dissector_add("cip.sc", dissect_class_svalidator_heur, "CIP Safety Validator", "s_validator_cip", proto_cip_class_s_validator, HEURISTIC_ENABLE); + + /* Register dissector for I/O data handling */ + dissector_add_for_decode_as("cip.io", cipsafety_base_data_handle ); + dissector_add_for_decode_as("cip.io", cipsafety_extended_data_handle ); + dissector_add_for_decode_as("cip.io", cipsafety_base_time_coord_handle ); + dissector_add_for_decode_as("cip.io", cipsafety_extended_time_coord_handle ); + + proto_cip = proto_get_id_by_filter_name( "cip" ); + subdissector_class_table = find_dissector_table("cip.class.iface"); +} + + +/* +* Editor modelines - https://www.wireshark.org/tools/modelines.html +* +* Local variables: +* c-basic-offset: 3 +* tab-width: 8 +* indent-tabs-mode: nil +* End: +* +* ex: set shiftwidth=3 tabstop=8 expandtab: +* :indentSize=3:tabSize=8:noTabs=true: +*/ |