-----------------------------------------------------------------------------------------------------
-- @file	pcan_gateway.lua
-- @author	U.Wilhelm, S.Mueller, S.Michaelsen
-- @see		www.peak-system.com
-- @brief	Lua Dissector for PEAK-System Ethernet and Wireless Gateways
-- @version	2.3
--
--
-- PEAK Ethernet & Wireless Gateway Protocol  Dissector Version 2.x
-- simple CAN over Ethernet Protocol dissector for products IPEH-004010 IPEH-004011, IPEH-004020(A), IPEH-004012 (CAN-FD)
-- (c) 2024 by PEAK-System Technik GmbH
-- This Software is open source - feel free to share and change it for your need!
--
-- Support UDP or TCP with multiple Ports
-- Default Port Range is 52000 to 52010
-- Default Protocol is UDP
--
-- V2.0: Extended script to work with 4 byte CRC at the end of the Block (CRC only over the CAN related Information)
-- V2.1: Extended script to work with Gateway FD (CAN-FD Frames)
-- V2.2: FIX Bug in "internal Flags" 
-- V2.3: - Fixed Bug in lenth calculation breaks dissection for WS >= 4.2.x
--       - Removed ranges from frame type counters
--       - separated messages included in frame for better navigation
--
-- HOW TO RUN THIS SCRIPT:
-- Wireshark and Tshark support multiple ways of loading Lua scripts: through a dofile() call in init.lua,
-- through the file being in either the global or personal plugins directories, or via the command line.
-- See the Wireshark USer's Guide chapter on Lua (https://wiki.wireshark.org/Lua).
-- Here the lines that you need to at at the end of the init.lua script (change the directory for your need):
--
--                 -- Add PEAK-System CAN Protocol at startup
--                 PEAK_PROTO_SCRIPT_PATH="D:\\WiresharkPortable\\"
--                 dofile(PEAK_PROTO_SCRIPT_PATH.."pcan_gateway.lua")
--
-- Once the script is loaded, it creates a new protocol named "peak-can" (or "PEAK-CAN" in some places).
-- If you have a capture file with packets in it which have data from a gateway, simply select one in the Packet List pane,
-- right-click on it, and select "Decode As ...", and then in the dialog box that shows up scroll down the list of protocols to one
-- called "PEAK-CAN", select that and click the "ok" or "apply" button.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
-- TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
-- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
-- OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
-- WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-- OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-- ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-----------------------------------------------------------------------------------------------------

-- declare our protocol
PEAK_Ethernet_proto = Proto("PEAK-CAN","CAN /CAN-FD Ethernet Gateway Protocol")

-- setup parameters
protocol_udp = 1
protocol_tcp = 2

-- available setting options for protocol
local pref_protocol_enum = {
    { 1,  "UDP", protocol_udp },
    { 2,  "TCP", protocol_tcp  },
}

-- default values for settings - change for your need!
local default_settings =
{
    expert_enabled	= false, 			-- default the Expert mode is off
    ports         	= "52000 - 52010",	-- default port(s) number for CAN Ethernet Packages - change for your need - accept also range settings..see LUA description
    protocol		= protocol_udp, 	-- default we use UDP
}

-- Settings for UI settings
PEAK_Ethernet_proto.prefs.infotext = Pref.statictext(
	"A PEAK-System Ethernet and WLAN Gateway Dissector for WireShark",
	"support the following Products: IPEH-004010/IPEH-004011/IPEH-004020(A)/IPEH-004012",
	"www.peak-system.com"
	)

PEAK_Ethernet_proto.prefs.can_ports = Pref.range(
	"Ports",
	default_settings.ports,
	"PCAN-Ethernet Listener Port",
	65535
	)

PEAK_Ethernet_proto.prefs.expert_mode = Pref.bool(
	"Show all available Info (Expert mode)",
	default_settings.expert_enabled,
	"Show also non user Data of protocol"
	)

PEAK_Ethernet_proto.prefs.transport_protokol = Pref.enum(
	"Select the used Protocol",
	default_settings.protocol,
	"set the UDP or TCP as protocol - TCP need a listener to get work",
	pref_protocol_enum
	)

-- PlugIn Info
local PEAK_Ethernet_plugin_info = {
            version = "2.3",
			description = "PEAK-System Ethernet and Wireless Gateway Protocol Filter",
            author = "U.Wilhelm, S.Mueller, S.Michaelsen",
			company = "PEAK-System Technik Germany",
            company_url = "http://www.peak-system.com"
			}
set_plugin_info(PEAK_Ethernet_plugin_info)

-- create a function to dissect it
function PEAK_Ethernet_proto.dissector(buffer,pinfo,tree)

	-- CONST
	local const_can_2ab = 0x80				-- 0x80 defines a CAN2.0A/2.0B Frame
	local const_can_2ab_crc = 0x81			-- 0x82 defines a CAN2.0A/2.0B with CRC Frame
	local const_can_fd = 0x90				-- 0x90 defines a CAN FD Frame
	local const_can_fd_crc = 0x91			-- 0x91 defines a CAN FD with CRC Frame
	local const_canlength_array = {1,2,3,4,5,6,7,8,12,16,20,24,32,48,64}

	--VAR
	local can_data_frame_len = buffer:len()
	local current_canmsg_startpos = 0

	--Counter
	local can_frames_counter = 0
	local can_frames_with_CRC_counter = 0
	local can_fd_frames_counter = 0
	local can_fd_with_crc_counter = 0
	local undefined_can_frames = 0

	local can_frame_counter = can_frames_counter+can_frames_with_CRC_counter+undefined_can_frames

	--VAR Buffer
	local current_canmsg_length_buffer
	local current_canmsg_type_buffer
	local current_canmsg_timstamplow_buffer
	local current_canmsg_timstamphigh_buffer
	local current_canmsg_tag_buffer
	local current_canmsg_channel_buffer
	local current_canmsg_flags_buffer
	local current_canmsg_canid_buffer
	local current_canmsg_dlc_buffer


	--Var
	local current_canmsg_length
	local current_canmsg_type_uint
	local current_canmsg_type_string
	local current_canmsg_timstamplow
	local current_canmsg_timstamphigh
	local current_canmsg_tag
	local current_canmsg_channel
	local current_canmsg_flags
	local current_canmsg_canid11bit
	local current_canmsg_canid29bit
	local current_canmsg_dlc


	-- Only if it is a TCP package, it could be fragmented -- could happened with MTU 1500
	-- Thanks to https://tewarid.github.io/2013/05/21/dealing-with-segmented-data-in-a-wireshark-dissector-written-in-lua.html
	if default_settings.protocol ==  protocol_tcp then
		local i = 0
		repeat
			if buffer:len() - i >= 2 then  -- length field is place in the first 2 Bytes -  see Developer PDF of PCAN-Gateway
				-- we have length field
				i = i + buffer(i,2):uint() -- read the 2 bytes and set the real len value
				if i > buffer:len() then
					-- we don't have all the data we need yet
					pinfo.desegment_len = i - buffer:len()
					return
				end
			else
				-- we don't have all of length field yet
				pinfo.desegment_len = DESEGMENT_ONE_MORE_SEGMENT
				-- PEAK_Ethernet_proto.dissector(buffer,pinfo,tree)
				return
			end
		until i >= buffer:len()
		-- rest of the dissector function remains the same,
	end

	-- show header
	local subtree_main = tree:add(PEAK_Ethernet_proto,buffer(),"CAN Protocol Data - PEAK-System Format")
	subtree_main:add(PEAK_Ethernet_proto,buffer(0,can_data_frame_len),"Complete CAN Data size in Frame" , "Length: " .. can_data_frame_len )
	while current_canmsg_startpos < can_data_frame_len -- get CAN Frame count
	do
		current_canmsg_length_buffer = buffer(current_canmsg_startpos,2)
		current_canmsg_type_buffer = buffer(current_canmsg_startpos+2,2)
		current_canmsg_length = current_canmsg_length_buffer:uint()
		current_canmsg_type_uint = current_canmsg_type_buffer:uint()
		current_canmsg_type_string = string.format("0x%02x",current_canmsg_type_uint)

		if current_canmsg_type_uint == const_can_2ab then
			can_frames_counter = can_frames_counter +1
		elseif current_canmsg_type_uint == const_can_2ab_crc then
			can_frames_with_CRC_counter = can_frames_with_CRC_counter+1
		elseif current_canmsg_type_uint == const_can_fd then
			can_fd_frames_counter = can_fd_frames_counter+1
		elseif current_canmsg_type_uint == const_can_fd_crc then
			can_fd_with_crc_counter = can_fd_with_crc_counter+1
		else
			undefined_can_frames = undefined_can_frames+1
		end

		current_canmsg_startpos = current_canmsg_startpos + current_canmsg_length
	end

	-- Shows number of found Messages
	if undefined_can_frames>0 then
		subtree_main:add(PEAK_Ethernet_proto,"Incompatible CAN Frames: " .. undefined_can_frames )
	end

	if can_frames_counter > 0 then
		subtree_main:add(PEAK_Ethernet_proto,"CAN Frames: " .. can_frames_counter )
	end

	if can_frames_with_CRC_counter > 0 then
		subtree_main:add(PEAK_Ethernet_proto,"CAN Frames with CRC: " .. can_frames_with_CRC_counter )
	end

	if can_fd_frames_counter > 0 then
		subtree_main:add(PEAK_Ethernet_proto,"CAN FD Frames: " .. can_fd_frames_counter )
	end

	if can_fd_with_crc_counter > 0 then
		subtree_main:add(PEAK_Ethernet_proto,"CAN FD Frames with CRC: " .. can_fd_with_crc_counter )
	end

	-- Set the Colums in the Receive List
	if default_settings.protocol ==  protocol_udp then
		pinfo.cols.protocol = "PEAK-CAN over UDP"
	else
		pinfo.cols.protocol = "PEAK-CAN over TCP"
	end

	can_frame_counter = can_frames_counter+can_frames_with_CRC_counter+undefined_can_frames+can_fd_frames_counter+can_fd_with_crc_counter

	pinfo.cols.info = "CAN Msg(s) in Frame: " .. can_frame_counter .. " "
	pinfo.cols.dst_port = default_settings.ports

	-- start formatting all messages
	current_canmsg_startpos = 0
	-- New Tree only for the real CAN Messages
	local subtree_can = subtree_main:add(PEAK_Ethernet_proto, "CAN Data Storage with " .. can_frame_counter - undefined_can_frames .." entrys")
	local i = 1
	while current_canmsg_startpos < can_data_frame_len do

		-- Fill buffers
		current_canmsg_length_buffer = buffer(current_canmsg_startpos,2)
		current_canmsg_type_buffer = buffer(current_canmsg_startpos+2,2)
		current_canmsg_timstamplow_buffer = buffer(current_canmsg_startpos+12,4)
		current_canmsg_timstamphigh_buffer = buffer(current_canmsg_startpos+16,4)
		current_canmsg_tag_buffer = buffer(current_canmsg_startpos+4,8)
		current_canmsg_channel_buffer = buffer(current_canmsg_startpos+20,1)
		current_canmsg_flags_buffer = buffer(current_canmsg_startpos+22,2)
		current_canmsg_canid_buffer = buffer(current_canmsg_startpos+24,4)
		current_canmsg_dlc_buffer = buffer(current_canmsg_startpos+21,1)

		-- Convert buffers
		current_canmsg_length = current_canmsg_length_buffer:uint()
		current_canmsg_type_uint = current_canmsg_type_buffer:uint()
		current_canmsg_type_string = string.format("0x%02x",current_canmsg_type_uint)
		current_canmsg_timstamplow = current_canmsg_timstamplow_buffer:uint()
		current_canmsg_timstamphigh = current_canmsg_timstamphigh_buffer:uint()
		current_canmsg_tag = current_canmsg_tag_buffer:string()
		current_canmsg_channel = current_canmsg_channel_buffer:uint()
		current_canmsg_flags = current_canmsg_flags_buffer:uint()
		current_canmsg_canid11bit = string.format("0x%03x",current_canmsg_canid_buffer:bitfield(3,29))
		current_canmsg_canid29bit = string.format("0x%08x",current_canmsg_canid_buffer:bitfield(3,29))
		current_canmsg_dlc = current_canmsg_dlc_buffer:uint()

		-- add message subtree
		local subtree_msg = subtree_can:add(PEAK_Ethernet_proto, buffer(current_canmsg_startpos, current_canmsg_length_buffer:uint()), "CAN Message " .. i)
		i=i+1
		if current_canmsg_type_uint == const_can_2ab or current_canmsg_type_uint == const_can_2ab_crc or current_canmsg_type_uint == const_can_fd  or current_canmsg_type_uint == const_can_fd_crc then
			-- unused blocks will in normal mode not show - only in EXPERT moded
			if PEAK_Ethernet_proto.prefs.expert_mode == true then
				-- not used - should be ignored)
				-- subtree_msg:add(current_canmsg_tag_buffer,"Tag: " .. current_canmsg_tag)
				subtree_msg:add(current_canmsg_channel_buffer,"CAN-Channel: " .. current_canmsg_channel)
				local subtree_can_flags = subtree_msg:add(PEAK_Ethernet_proto,current_canmsg_flags_buffer,"internal Flags:")
				if current_canmsg_type_uint == const_can_2ab or current_canmsg_type_uint == const_can_2ab_crc then
					if current_canmsg_flags_buffer:bitfield(15,1) == 1 then
						subtree_can_flags:add(current_canmsg_flags_buffer,"- Remote Transmission Request")
					end
					if  current_canmsg_flags_buffer:bitfield(14,1) == 1 then
						subtree_can_flags:add(current_canmsg_flags_buffer,"- Extended ID")
					end
				elseif current_canmsg_type_uint == const_can_fd or current_canmsg_type_uint == const_can_fd_crc then
					if current_canmsg_flags_buffer:bitfield(14,1) == 1 then
						subtree_can_flags:add(current_canmsg_flags_buffer,"- Extended ID")
					end
					if current_canmsg_flags_buffer:bitfield(11,1) == 1 then
						subtree_can_flags:add(current_canmsg_flags_buffer,"- Extended Data Length")
					end
					if current_canmsg_flags_buffer:bitfield(10,1) == 1 then
						subtree_can_flags:add(current_canmsg_flags_buffer,"- Bit Rate Switch")
					end
					if current_canmsg_flags_buffer:bitfield(9,1) == 1 then
						subtree_can_flags:add(current_canmsg_flags_buffer,"- Error State Indicator")
					end
				end

				if current_canmsg_type_uint == const_can_2ab then
					subtree_msg:add(current_canmsg_type_buffer,"CAN MessageType: CAN2.0A/2.0B Frame")
				elseif current_canmsg_type_uint == const_can_2ab_crc then
					subtree_msg:add(current_canmsg_type_buffer,"CAN MessageType: CAN2.0A/2.0B with CRC Frame")
				elseif	current_canmsg_type_uint == const_can_fd then
					subtree_msg:add(current_canmsg_type_buffer,"CAN MessageType: CAN FD Frame")
				elseif	current_canmsg_type_uint == const_can_fd_crc then
					subtree_msg:add(current_canmsg_type_buffer,"CAN MessageType: CAN FD with CRC Frame")
				end

			end
			subtree_msg:add(current_canmsg_timstamplow_buffer,"TimeStamp-Low in µS: " .. current_canmsg_timstamplow)
			subtree_msg:add(current_canmsg_timstamphigh_buffer,"TimeStamp-High in µS: " .. current_canmsg_timstamphigh)

			local id_flag_buffer = current_canmsg_canid_buffer
			-- Get the Bits 3 to 32 - keep in mind how the Data is stored !!! - correspond to Bit 0..28 in ID field
			can_id = id_flag_buffer:bitfield(3,29)
			-- Get the Bit 0 - correspond to Bit 32 in ID field
			can_msg_type = id_flag_buffer:bitfield(0,1)
			-- Get the Bit 1 - correspond to Bit 31 in ID field
			can_rtr_flag = id_flag_buffer:bitfield(1,1)

			-- show Msg.ID
			if can_msg_type == 0 then -- 11Bit ID
				subtree_msg:add(current_canmsg_canid_buffer,"CAN-ID: " .. current_canmsg_canid11bit)
			else  --29 Bit CAN ID
				subtree_msg:add(current_canmsg_canid_buffer,"CAN-ID: " .. current_canmsg_canid29bit)
			end
			-- Set Value for Msgtyoe
			if can_msg_type == 0 then
				subtree_msg:add(buffer(24+current_canmsg_startpos,1),"Std. 11Bit Msg.")
			else
				subtree_msg:add(buffer(24+current_canmsg_startpos,1),"Ext. 29Bit Msg.")
			end
			-- show if RTR Frame
			if can_rtr_flag == 1 then
				subtree_msg:add(buffer(24+current_canmsg_startpos,1),"RTR Frame")
			end

			-- show the CAN DLC
			subtree_msg:add(current_canmsg_dlc_buffer,"DLC: " .. current_canmsg_dlc)
			tmp_dlc = 0 -- counter to zero
			local breakN = 0
			local db_info_txt = ""
			while tmp_dlc < const_canlength_array[current_canmsg_dlc] do
				if tmp_dlc < 10 then
					db_info_txt = db_info_txt .. "DB0" .. tmp_dlc .. ":" ..  string.format("0x%02x",buffer(28+tmp_dlc+current_canmsg_startpos,1):uint()) .. " "
				else
					db_info_txt = db_info_txt .. "DB" .. tmp_dlc .. ":" ..  string.format("0x%02x",buffer(28+tmp_dlc+current_canmsg_startpos,1):uint()) .. " "
				end
				tmp_dlc = tmp_dlc + 1
				if (tmp_dlc%8) == 0 or tmp_dlc == const_canlength_array[current_canmsg_dlc] then
					if (tmp_dlc%8) ~= 0 then
						subtree_msg:add(buffer(28+current_canmsg_startpos+breakN,tmp_dlc%8),db_info_txt)
					else
						subtree_msg:add(buffer(28+current_canmsg_startpos+breakN,8),db_info_txt)
					end
					db_info_txt = ""
					number_counter = 0
					breakN = tmp_dlc
				end
			end

			-- show CRC
			if current_canmsg_type_uint == const_can_2ab_crc or current_canmsg_type_uint == const_can_fd_crc then
				current_canmsg_crc_buffer = buffer(current_canmsg_startpos+current_canmsg_length-4,4)
				subtree_msg:add(current_canmsg_crc_buffer,"CRC: " .. current_canmsg_crc_buffer:uint())
			end
		else
			current_canmsg_crc_buffer = buffer(current_canmsg_startpos,current_canmsg_length)
			subtree_msg:add(buffer(current_canmsg_startpos,current_canmsg_length),"Incompatible CAN Format")
		end
		-- subtree_msg:add("--------------------------------------------------------------------------------")
		current_canmsg_startpos = current_canmsg_startpos + current_canmsg_length
	end

end

-- Load the default port into the DissectorTable
if PEAK_Ethernet_proto.prefs.transport_protokol == protocol_udp then
	-- load the udp.port table
	udp_table = DissectorTable.get("udp.port")
	--register our protocol to handle udp port
	udp_table:add(PEAK_Ethernet_proto.prefs.can_ports,PEAK_Ethernet_proto)
else
	-- register the TCP port
	tcp_table = DissectorTable.get("tcp.port")
	-- register our protocol to handle tcp port
	tcp_table:add(PEAK_Ethernet_proto.prefs.can_ports, PEAK_Ethernet_proto)
end

----------------------------------------------
-- a function for handling prefs being changed
function PEAK_Ethernet_proto.prefs_changed()

	-- remove the current Tabel entry (default_settings)
	if default_settings.protocol == protocol_udp then
		-- UDP
		DissectorTable.get("udp.port"):remove(default_settings.ports, PEAK_Ethernet_proto)
	else
		-- TCP
		DissectorTable.get("tcp.port"):remove(default_settings.ports, PEAK_Ethernet_proto)
	end


	-- Now set the new protocol with the new port
	if PEAK_Ethernet_proto.prefs.transport_protokol == protocol_udp then
		--UDP
		-- load the udp.port table
		udp_table = DissectorTable.get("udp.port")
		udp_table:add(PEAK_Ethernet_proto.prefs.can_ports,PEAK_Ethernet_proto)
	else
		-- TCP
		-- load the tcp.port table
		tcp_table = DissectorTable.get("tcp.port")
		tcp_table:add(PEAK_Ethernet_proto.prefs.can_ports, PEAK_Ethernet_proto)
	end

	-- set our new default port
	default_settings.ports = PEAK_Ethernet_proto.prefs.can_ports
	-- set our new default protocol
	default_settings.protocol = PEAK_Ethernet_proto.prefs.transport_protokol

end

