"""
Controller Area Network, ISO 11898-1, see https://en.wikipedia.org/wiki/CAN_bus
SocketCAN message format handling

API examples:
https://github.com/rhyttr/SocketCAN/tree/3c46872d9af0885b42526b70853400c6d94b7c54/can-utils
"""
import logging
import sys

from pypacker import pypacker

logger = logging.getLogger("pypacker")

module_this = sys.modules[__name__]


"""
OBDII modes:

01 	Show current data
02 	Show freeze frame data
03 	Show stored Diagnostic Trouble Codes
04 	Clear Diagnostic Trouble Codes and stored values
05 	Test results, oxygen sensor monitoring (non CAN only)
06 	Test results, other component/system monitoring
07 	Show pending Diagnostic Trouble Codes (detected during current or last driving cycle)
08 	Control operation of on-board component/system
09 	Request vehicle information
0A 	Permanent Diagnostic Trouble Codes (DTCs) (Cleared DTCs)
"""


OBD2_MODE_01_PIDS = {
	0x00: "PIDS_SUPPORTED_1_20",
	0x01: "MONITOR_STATUS",
	0x02: "FREEZE_DTC",
	0x03: "FUEL_SYSTEM_STATUS",
	0x04: "CALCULATED_ENGINE_LOAD",
	0x05: "ENGINE_COOLANT_TEMP",
	0x06: "SHORT_TERM_FUEL_BANK1",
	0x07: "LONG_TERM_FUEL_BANK1",
	0x08: "SHORT_TERM_FUEL_BANK2",
	0x09: "LONG_TERM_FUEK_BANK2",
	0x0A: "FUELPRESSURE",
	0x0B: "INTAKE_MANIFOLD_PRESSURE",
	0x0C: "ENGINE_RPM",
	0x0D: "VEHICLE_SPEED",
	0x0E: "TIMING_ADVANCE",
	0x0F: "INTAKGE_AIR_TEMP",
	0x10: "MAF_AIR_FLOW",
	0x11: "THROTTLE_POS",
	0x12: "COMMANDED_SECONDARY_AIR_STATUS",
	0x13: "OXYGEN_SENSORS_PRESENT",
	0x14: "OXYGEN_SENSOR1",
	0x15: "OXYGEN_SENSOR2",
	0x16: "OXYGEN_SENSOR3",
	0x17: "OXYGEN_SENSOR4",
	0x18: "OXYGEN_SENSOR5",
	0x19: "OXYGEN_SENSOR6",
	0x1A: "OXYGEN_SENSOR7",
	0x1B: "OXYGEN_SENSOR8",
	0x1C: "OBD_STANDARDS_",
	0x1D: "OXYGEN_SENSORS",
	0x1E: "AUXILIARY_INPUT_STATUS",
	0x1F: "RUNTIME_SINCE_ENGINE",
	0x20: "PIDS_SUPPORTED_21_40",
	0x21: "DISTANCE_TRAVELED_WITH_MALFUNCTION",
	0x22: "FUEL_RAIL_PRESSURE",
	0x23: "FUEL_RAIL_GAUGE_PRESSURE",
	0x24: "OXYGEN_SENSOR1",
	0x25: "OXYGEN_SENSOR2",
	0x26: "OXYGEN_SENSOR3",
	0x27: "OXYGEN_SENSOR4",
	0x28: "OXYGEN_SENSOR5",
	0x29: "OXYGEN_SENSOR6",
	0x2A: "OXYGEN_SENSOR7",
	0x2B: "OXYGEN_SENSOR8",
	0x2C: "COMMANDED_EGR",
	0x2D: "EGR_ERROR",
	0x2E: "COMMANDED_EVAPORATIVE_PURGE",
	0x2F: "FUEL_TANK_LEVEL_INPUT",
	0x30: "WARMUPS_SINCE_CODES_CLEARED",
	0x31: "DISTANCE_TRAVELED_SINCE_CODES_CLEARED",
	0x32: "EVAP_SYSTEM_VAPOR_PRESSURE",
	0x33: "ABSOLUTE_BAROMETIC_PRESSURE",
	0x34: "OXYGEN_SENSOR1_FUELAIR",
	0x35: "OXYGEN_SENSOR2_FUELAIR",
	0x36: "OXYGEN_SENSOR3_FUELAIR",
	0x37: "OXYGEN_SENSOR4_FUELAIR",
	0x38: "OXYGEN_SENSOR5_FUELAIR",
	0x39: "OXYGEN_SENSOR6_FUELAIR",
	0x3A: "OXYGEN_SENSOR7_FUELAIR",
	0x3B: "OXYGEN_SENSOR8_FUELAIR",
	0x3C: "CATALYST_TEMP_B1S1",
	0x3D: "CATALYST_TEMP_B2S1",
	0x3E: "CATALYST_TEMP_B1S2",
	0x3F: "CATALYST_TEMP_B2S2",
	0x40: "PIDS_SUPPORTED_41_60",
	0x41: "MONITOR_STATUS_",
	0x42: "CTRL_MODULE_VOLTAGE",
	0x43: "ABS_LOAD_VALUE",
	0x44: "FUEL_AIR_COMMANDED",
	0x45: "RELATIVE_THROTTLE_POS",
	0x46: "AMBIENT_AIR_TEMP",
	0x47: "ABSOLUTE_THROTTLE_POS_B",
	0x48: "ABSOLUTE_THROTTLE_POS_C",
	0x49: "ABSOLUTE_THROTTLE_POS_D",
	0x4A: "ABSOLUTE_THROTTLE_POS_E",
	0x4B: "ABSOLUTE_THROTTLE_POS_F",
	0x4C: "COMMANDED_THROTTLE_ACTUATOR",
	0x4D: "TIME_RUN_WITH_MIL",
	0x4E: "TIME_SINCE_TROUBLE_CODES_CLEARED",
	0x4F: "MAX_FUEL_AIR_EQ_RATIO",
	0x50: "MAX_AIR_FLOW",
	0x51: "FUEL_TYPE",
	0x52: "ETHANOL_FUEL",
	0x53: "ABS_EVAP_SYSTEM_VAPOR_PRESSURE",
	0x54: "EVAP_SYSTEM_VAPOR_PRESSURE",
	0x55: "SHORT_TERM_SECONDARY_OXYGEN_1_3",
	0x56: "LONG_TERM_SECONDARY_OXYGEN_1_3",
	0x57: "SHORT_TERM_SECONDARY_OXYGEN_2_4",
	0x58: "LONG_TERM_SECONDARY_OXYGEN_2_4",
	0x59: "FUEL_RAIL_ABS_PRESSURE",
	0x5A: "RELATIVE_ACCELERATOR",
	0x5B: "HYBRID_BATTERY_PACK",
	0x5C: "ENGINE_OIL_TEMP",
	0x5D: "FUEL_INJECTION_TIMING",
	0x5E: "ENGINE_FUEL_RATE",
	0x5F: "EMISSION_REQUIREMENTS",
	0x60: "PIDS_SUPPORTED_61_80",
	0x61: "DEMAND_ENGINE",
	0x62: "ACTUAL_ENGINE",
	0x63: "ENGINE_REF_TORQUE",
	0x64: "ENGINE_PERCENT_TORQUE",
	0x65: "AUXILIARY_INPUT",
	0x66: "MASS_AIR_FLOW_SENSOR",
	0x67: "ENGINE_COOLANT_TEMP",
	0x68: "INTAKE_AIR_TEMP_SENSOR",
	0x69: "COMMANDED_EGR_ERROR",
	0x6A: "COMMANDED_DIESEL_INTAKE",
	0x6B: "EXHAUST_GAS_RECIRCULATION",
	0x6C: "COMMANDED_THROTTLE_ACTUATOR",
	0x6D: "FUEL_PRESSURE_CONTROL_SYSTEM",
	0x6E: "INJECTION_PRESSURE_CONTROL_SYSTEM",
	0x6F: "TURBOCHARGER_COMPRESSOR",
	0x70: "BOOST_PRESSURE",
	0x71: "VARIABLE_GEOMETRY",
	0x72: "WASTEGATE_CONTROL",
	0x73: "EXHAUST_PRESSURE",
	0x74: "TURBOCHARGER_RPM",
	0x75: "TURBOCHARGER_TEMP",
	0x76: "TURBOCHARGER_TEMP",
	0x77: "CHARGER_AIR_COOLER_TEMP",
	0x78: "EXHAUST_GAS_TEMP",
	0x79: "EXHAUST_GAS_TEMP",
	0x7A: "DIESEL_PARTICULATE_FILTER",
	0x7B: "DIESEL_PARTICULATE_FILTER",
	0x7C: "DIESEL_PARTICULATE_FILTER_TEMP",
	0x7D: "NOX_CTRL_AREA",
	0x7E: "PM_NTE_CTLR_AREA",
	0x7F: "ENGINE_RUN_TIME",
	0x80: "PIDS_SUPPORTED_81_A0",
	0x81: "ENGINE_RUNTIME_AUXILIARY",
	0x82: "ENGINE_RUNTIME_AUXILIARY",
	0x83: "NOX_SENSOR",
	0x84: "MANIFOLD_SURFACE_TEMP",
	0x85: "NOX_REAGENT_SYSTEM",
	0x86: "PARTICULATE_MATTER_SENSOR",
	0x87: "INTAKE_MANIFOLD_PRESSURE",
	0xA0: "PIDS_SUPPORTED_A1_C0",
	0xC0: "PIDS_SUPPORTED_C1_E0",
	0xC3: "?",
	0xC4: "?"
}

OBD2_MODE_02_PIDS = {
	0x02: "DTC_FREEZE_STORED",
}

OBD2_MODE_05_PIDS = {
	0x0100: "OBD_MONITOR_IDS",
	0x0101: "O2_SENSOR_1_1",
	0x0102: "O2_SENSOR_1_2",
	0x0103: "O2_SENSOR_1_3",
	0x0104: "O2_SENSOR_1_4",
	0x0105: "O2_SENSOR_2_1",
	0x0106: "O2_SENSOR_2_2",
	0x0107: "O2_SENSOR_2_3",
	0x0108: "O2_SENSOR_2_4",
	0x0109: "O2_SENSOR_3_1",
	0x010A: "O2_SENSOR_3_2",
	0x010B: "O2_SENSOR_3_3",
	0x010C: "O2_SENSOR_3_4",
	0x010D: "O2_SENSOR_4_1",
	0x010E: "O2_SENSOR_4_2",
	0x010F: "O2_SENSOR_4_3",
	0x0110: "O2_SENSOR_4_4",
	0x0201: "O2_SENSOR_1_1",
	0x0202: "O2_SENSOR_1_2",
	0x0203: "O2_SENSOR_1_3",
	0x0204: "O2_SENSOR_1_4",
	0x0205: "O2_SENSOR_2_1",
	0x0206: "O2_SENSOR_2_2",
	0x0207: "O2_SENSOR_2_3",
	0x0208: "O2_SENSOR_2_4",
	0x0209: "O2_SENSOR_3_1",
	0x020A: "O2_SENSOR_3_2",
	0x020B: "O2_SENSOR_3_3",
	0x020C: "O2_SENSOR_3_4",
	0x020D: "O2_SENSOR_4_1",
	0x020E: "O2_SENSOR_4_2",
	0x020F: "O2_SENSOR_4_3",
	0x0210: "O2_SENSOR_4_4"
}

OBD2_MODE_0A_PIDS = {
	0x00: "MODE_9_SUPPORTED_PIDS_1_20",
	0x01: "VIN_MESSAGE_COUNT",
	0x02: "VIN",
	0x03: "CALIBRATION_ID_MESSAGE_COUNT",
	0x04: "CALLIBRATION_ID",
	0x05: "CVN_MESSAGE_COUNT",
	0x06: "CVN",
	0x07: "PERFORMANCE_TRACKING_MESSAGE_COUNT",
	0x08: "PERFORMANCE_TRACKING",
	0x09: "ECU_NAME_MESSAGE_COUNT",
	0x0A: "ECU_NAME",
	0x0B: "PERFORMANCE_TRACKING_COMPRESSION"
}

OBD2_MODE_DESCR = {
	0x01: "OBD2_MODE_SHOW_CURRENT_DATA1",
	0x02: "OBD2_MODE_SHOW_CURRENT_DATA2",
	0x03: "OBD2_MODE_SHOW_STORED_TROUBLECODES",
	0x04: "OBD2_MODE_CLEAR_DIAGNOSTIC_TROUBLECODES",
	0x05: "OBD2_MODE_TEST_RESULTS_OXYGEN",
	0x06: "OBD2_MODE_TEST_RESULTS_OTHER",
	0x07: "OBD2_MODE_SHOW_PENDING_DIAGNOSTIC_TROUBLECODES",
	0x08: "OBD2_MODE_CONTROL_OPERATION",
	0x09: "OBD2_MODE_REQUEST_VEHICLE_INFO",
	0x0A: "OBD2_MODE_PERMANENT_DIAGNOSTIC_TROUBLECODES"
}

for obdid, name in OBD2_MODE_DESCR.items():
	setattr(module_this, name, obdid)

# Diagnostic and Communications Management
UDS_SID_DESCR = {
	0x10: "UDS_SID_DIAGNOSTIC_SESSION_CONTORL",
	0x11: "UDS_SID_ECU_RESET",
	0x12: "UDS_SID_GMLAN_READ_FAILURE_RECORD",
	0x14: "UDS_SID_CLEAR_DIAGNOSTIC_INFORMATION",
	0x19: "UDS_SID_READ_DTC_INFORMATION",
	0x1A: "UDS_SID_GMLAN_READ_DIAGNOSTIC_ID",
	0x20: "UDS_SID_RETURN_TO_NORMAL",
	0x22: "UDS_SID_READ_DATA_BY_ID",
	0x23: "UDS_SID_READ_MEMORY_BY_ADDRESS",
	0x24: "UDS_SID_READ_SCALING_DATA_BY_ID",
	0x27: "UDS_SID_SECURITY_ACCESS",
	0x28: "UDS_SID_COMMUNICATION_CONTROL",
	0x2A: "UDS_SID_READ_DATA_BY_PERIODIC_ID",
	0x2C: "UDS_SID_DYNAMICALLY_DEFINE_DATA_ID",
	0x2D: "UDS_SID_DEFINE_PID_BY_MEMORY_ADDRESS",
	0x2E: "UDS_SID_WRITE_DATA_BY_ID",
	0x2F: "UDS_SID_IO_CONTORL_BY_ID",
	0x31: "UDS_SID_ROUTINE_CONTROL",
	0x34: "UDS_SID_REQUEST_DOWNLOAD",
	0x35: "UDS_SID_REQUEST_UPLOAD",
	0x36: "UDS_SID_TRANSFER_DATA",
	0x37: "UDS_SID_REQUEST_TANSFER_EXIT",
	0x38: "UDS_SID_REQUEST_FILE_TRANSFER",
	0x3B: "UDS_SID_GMLAN_WRITE_DID",
	0x3D: "UDS_SID_WRITE_MEMORY_BY_ADDRESS",
	0x3E: "UDS_SID_TESTER_PRESENT",
	0x83: "UDS_SID_ACCESS_TIMING_PARAMETER",
	0x84: "UDS_SID_SECURED_TRANSMISSION",
	0x85: "UDS_SID_CONTORL_DTC_SETTING",
	0x86: "UDS_SID_RESPONSE_ON_EVENT",
	0x87: "UDS_SID_LINK_CONTORL",
	0xA2: "UDS_SID_GMLAN_REPORT_PROGRAMMING_STATE",
	0xA5: "UDS_SID_GMLAN_ENTER_PROGRAMMING_MODE",
	0xA9: "UDS_SID_GMLAN_CHECK_CODES",
	0xAA: "UDS_SID_GMLAN_READ_DPID",
	0xAE: "UDS_SID_GMLAN_DEVICE_CONTROL",
}

for udsid, name in UDS_SID_DESCR.items():
	setattr(module_this, name, udsid)

# UDS = 0x7F [SID_requested] [NRC]
UDS_NRC_DESCR = {
	0x10: "UDS_NRC_GENERAL_REJECT",
	0x11: "UDS_NRC_SERVICE_NOT_SUPPORTED",
	0x12: "UDS_NRC_SUBFUNCTION_NOT_SUPPORTED",
	0x13: "UDS_NRC_INCORRECT_MESSAGE_LENGTH_OR_FORMAT",
	0x14: "UDS_NRC_RESPONSE_TOO_BIG",
	0x21: "UDS_NRC_BUSY_REPEAT_REQUEST",
	0x22: "UDS_NRC_CONDITION_NOT_CORRECT",
	0x24: "UDS_NRC_REQUEST_SEQUENCE_ERROR",
	0x25: "UDS_NRC_NONRESPONSE_FROM_SUBNET_COMPONENT",
	0x26: "UDS_NRC_FAILURE_PREVENTS_EXEC_OR_ACTION",
	0x31: "UDS_NRC_REQUEST_OUT_OF_RANGE",
	0x33: "UDS_NRC_SECURITY_ACCESS_DENIED",
	0x35: "UDS_NRC_INVALID_KEY",
	0x36: "UDS_NRC_EXCEEDED_NUMBER_OF_ATTEMPTS",
	0x37: "UDS_NRC_REQUIRED_TIME_DELEAY_NOT_EXPIRED",
	0x70: "UDS_NRC_UPLOAD_DOWNLOAD_NOT_ACCEPTED",
	0x71: "UDS_NRC_TRANSFER_DATA_SUSPENDED",
	0x72: "UDS_NRC_GENERAL_PROGRAMMING_FAILURE",
	0x73: "UDS_NRC_WRONG_BLOCK_SEQUENCE_COUNTER",
	0x78: "UDS_NRC_REQUEST_CORRECTLY_RESPONSE_PENDING",
	0x7E: "UDS_NRC_SUBFUNCTION_NOT_SUPPORTED_IN_SESSION",
	0x7F: "UDS_NRC_SERVICE_NOT_SUPPORTED_IN_SESSION"
}

for x in range(0x38, 0x4F):
	UDS_NRC_DESCR[x] = "UDS_NRC_RESERVED_%X" % x

for udsid, name in UDS_NRC_DESCR.items():
	setattr(module_this, name, udsid)


class OBD2(pypacker.Packet):
	__hdr__ = (
		("mode", "B", 0),
		("pid", "B", 0)
	)


class UDS(pypacker.Packet):
	__hdr__ = (
		("sid", "B", 0),
		("lev", "B", 0)
	)

	def _dissect(self, buf):
		# TODO: lev not always present
		return 2


ISOTP_TYPE_SF	= 0x00  # single frame
ISOTP_TYPE_FF	= 0x01  # first frame
ISOTP_TYPE_CF	= 0x02  # consecutive frame
ISOTP_TYPE_FC	= 0x03  # flow control


types_isotp_offset_upper = {
	ISOTP_TYPE_SF: 1,
	ISOTP_TYPE_FF: 2,
	ISOTP_TYPE_CF: 1,
	ISOTP_TYPE_FC: 3
}

types_isotp_offset_upper_got_type = {ISOTP_TYPE_SF, ISOTP_TYPE_FF}


class ISOTPBase(pypacker.Packet):
	def __get_sig(self):
		return (self.pci & 0xF0) >> 4

	def __set_sig(self, value):
		self.pci = (value & 0xF) << 4 | (self.pci & 0xF)

	sig = property(__get_sig, __set_sig)

	def _dissect(self, buf):
		#logger.debug("dissect in base class")
		# OBD/UDS can one be differentiated in ISOTP_TYPE_SF and ISOTP_TYPE_FF
		sig = buf[0] >> 4
		#logger.debug("bytes in ISOTP: %r" % buf)
		#logger.debug("ISOTP type: %X, offset: %d" % (sig, types_isotp_offset_upper[sig]))
		#logger.debug("upper bytes: %r" % buf[types_isotp_offset_upper[sig]: ])

		hlen = types_isotp_offset_upper[sig]

		# check by request/response SID, on both OBD2 and UDS response will be SID+0x40
		if sig in types_isotp_offset_upper_got_type or (sig - 0x40) in types_isotp_offset_upper_got_type:
			obd_mode = buf[types_isotp_offset_upper[sig]]

			if obd_mode in OBD2_MODE_DESCR:
				# Assume OBD2
				#logger.debug("got OBD2")
				return hlen, 0
			else:
				# Assume UDS
				#logger.debug("got UDS, will use bytes: %r" % buf[types_isotp_offset_upper[sig]: ])
				return hlen, 1

		return hlen


class ISOTPSingleFrame(ISOTPBase):
	__hdr__ = (
		("pci", "B", ISOTP_TYPE_SF),
	)

	def __get_dl(self):
		return self.pci & 0xF

	def __set_dl(self, value):
		self.pci = (self.pci & 0xF0) | (value & 0xF)

	dl = property(__get_dl, __set_dl)

	__handler__ = {
		0: OBD2, 1: UDS
	}


class ISOTPFirstFrame(ISOTPBase):
	__hdr__ = (
		("pci", "H", ISOTP_TYPE_FF),
	)

	__handler__ = {
		0: OBD2, 1: UDS
	}

	def __get_sig(self):
		return (self.pci & 0xF000) >> 12

	def __set_sig(self, value):
		self.pci = (value & 0xF) << 12 | (self.pci & 0xFFF)

	sig = property(__get_sig, __set_sig)

	def __get_dl(self):
		return self.pci & 0xFFF

	def __set_dl(self, value):
		self.pci = (self.pci & 0xF000) | (value & 0xFFF)

	dl = property(__get_dl, __set_dl)


class ISOTPConsecutiveFrame(ISOTPBase):
	__hdr__ = (
		("pci", "B", ISOTP_TYPE_CF),
	)

	__handler__ = {
		0: OBD2, 1: UDS
	}

	def __get_sn(self):
		return self.pci & 0xF

	def __set_sn(self, value):
		self.pci = (self.pci & 0xF0) | (value & 0xF)

	sn = property(__get_sn, __set_sn)


class ISOTPFlowControl(ISOTPBase):
	__hdr__ = (
		("pci", "B", ISOTP_TYPE_FC),
		("pci_blocksize", "B", 0),
		("pci_minsep", "B", 0),
	)

	__handler__ = {
		0: OBD2, 1: UDS
	}

	def __get_flowstatus(self):
		return self.pci & 0xF

	def __set_flowstatus(self, value):
		self.pci = (self.pci & 0xF0) | (value & 0xF)

	flowstatus = property(__get_flowstatus, __set_flowstatus)


isotp_type_class = {
	ISOTP_TYPE_SF: ISOTPSingleFrame,
	ISOTP_TYPE_FF: ISOTPFirstFrame,
	ISOTP_TYPE_CF: ISOTPConsecutiveFrame,
	ISOTP_TYPE_FC: ISOTPFlowControl
}

# CAN flags, little endian..wtf?
CAN_MASK_EXT		= 0x80000000
CAN_MASK_EXT_SHIFT	= 31
CAN_MASK_RTR		= 0x40000000
CAN_MASK_RTR_SHIFT	= 30
CAN_MASK_ERR		= 0x20000000
CAN_MASK_ERR_SHIFT	= 29
# big endian
MASK_ID			= 0x1FFFFFFF
MASK_FLAGS		= ~MASK_ID


ISOTP_PKT_CLZES = {ISOTPConsecutiveFrame, ISOTPFirstFrame, ISOTPFlowControl, ISOTPSingleFrame}


class CAN(pypacker.Packet):
	"""
		Format:

	The field containing the CAN ID and flags is in network byte order (big-endian).
	The bottom 29 bits contain the CAN ID of the frame. The remaining bits are:

	0x20000000 - set if the frame is an error message rather than a data frame.
	0x40000000 - set if the frame is a remote transmission request frame.
	0x80000000 - set if the frame is an extended 29-bit frame rather than a standard 11-bit frame. frame.
	Source: http://www.tcpdump.org/linktypes/LINKTYPE_CAN_SOCKETCAN.html


	SocketCan Packet, see https://www.kernel.org/doc/Documentation/networking/can.txt

	struct can_frame {
		canid_t can_id;  /* 32 bit CAN_ID + EFF/RTR/ERR flags */
		__u8    can_dlc; /* frame payload length in byte (0 .. 8) */
		__u8    __pad;   /* padding */
		__u8    __res0;  /* reserved / padding */
		__u8    __res1;  /* reserved / padding */
		__u8    data[8] __attribute__((aligned(8)));
	};
	special address description flags for the CAN_ID
		CAN_EFF_FLAG 0x80000000U /* EFF/SFF is set in the MSB */
		CAN_RTR_FLAG 0x40000000U /* remote transmission request */
		CAN_ERR_FLAG 0x20000000U /* error message frame */

	Native CAN structure (on wire):

	CAN lengths (11-bit ID):
	- Arbitration Field (12)
	-- ID: unique id/prio (11)
	-- Remote transmission request: 0/dominant for data, 1 for remote request (1)
	- Control (6)
	-- ID ext.: 0 for 11-bit ID (1)
	-- Reserved: must be 0 (1)
	-- Data length code: Number of bytes, 0 up to 8 (4)
	- Data: 0 up to 8 bytes (8)

	CAN lengths (29-bit ID):
	- Arbitration Field (40)
	-- ID: unique id/prio (11)
	-- Substitute remote request, must be 1 (1)
	-- Identifier extension bit: must be 1 (1)
	-- ID: unique id/prio (18)
	-- Remote transmission request: 0/dominant for data, 1 for remote request (1)
	- Control (6)
	-- Reserved: must be 0 (2)
	-- Data length code: Number of bytes, 0 up to 8 (4)
	- Data: 0 up to 8 bytes (8)

	"""
	__hdr__ = (
		("flag_id", "I", 0),
		("dlc", "B", 0),
		("pad", "B", 0),
		("res1", "B", 0),
		("res2", "B", 0)
	)

	__handler__ = {
		ISOTP_TYPE_SF: ISOTPSingleFrame,
		ISOTP_TYPE_FF: ISOTPFirstFrame,
		ISOTP_TYPE_CF: ISOTPConsecutiveFrame,
		ISOTP_TYPE_FC: ISOTPFlowControl
	}

	def __get_extended(self):
		return 0 if (self.flag_id & CAN_MASK_EXT) == 0 else 1

	def __set_extended(self, value):
		self.flag_id = (self.flag_id & ~CAN_MASK_EXT) | ((value & 1) << CAN_MASK_EXT_SHIFT)

	extended = property(__get_extended, __set_extended)

	def __get_rtr(self):
		return 0 if (self.flag_id & CAN_MASK_RTR) == 0 else 1

	def __set_rtr(self, value):
		self.flag_id = (self.flag_id & ~CAN_MASK_RTR) | ((value & 1) << CAN_MASK_RTR_SHIFT)

	rtr = property(__get_rtr, __set_rtr)

	def __get_err(self):
		return 0 if (self.flag_id & CAN_MASK_ERR) == 0 else 1

	def __set_err(self, value):
		self.flag_id = (self.flag_id & ~CAN_MASK_ERR) | ((value & 1) << CAN_MASK_ERR_SHIFT)

	err = property(__get_err, __set_err)

	def __get_id(self):
		return self.flag_id & MASK_ID

	def __set_id(self, value):
		flags_be = self.flag_id & MASK_FLAGS
		self.flag_id = flags_be | (value & MASK_ID)

	id = property(__get_id, __set_id)

	def _dissect(self, buf):
		# assume ISO-TP
		isotp_type = (buf[8] & 0xF0) >> 4
		#logger.debug("got ISOTP type: %d, class will be: %r" %
		#(isotp_type, isotp_type_class[isotp_type]))
		return 8, isotp_type

	def _update_fields(self):
		if not self._header_value_changed:
			return

		if self.id > 0x7FF:
			self.extended = 1
		else:
			self.extended = 0

	def show_packet(self, prefix=""):
		if self.body_handler.__class__ not in ISOTP_PKT_CLZES:
			logger.warning("not an isotp packet: %r", self.body_handler)
			return

		isotp_pkt = self.body_handler
		output = []
		output.append("%s [ID: 0x%05X] " % (prefix, self.id))

		uds_pkt = self[UDS]

		if uds_pkt is not None:
			if uds_pkt.sid in UDS_SID_DESCR:
				output.append("-> %s" % UDS_SID_DESCR.get(uds_pkt.sid))
			elif uds_pkt.sid - 0x40 in UDS_SID_DESCR:
				output.append("<- %s" % UDS_SID_DESCR.get(uds_pkt.sid - 0x40))
			elif uds_pkt.sid == module_this.UDS_NRC_SERVICE_NOT_SUPPORTED_IN_SESSION:
				output.append("Err: %s" % UDS_NRC_DESCR.get(uds_pkt.bin()[2], "0x%X" % uds_pkt.bin()[2]))
			else:
				output.append("<unknown: %s>" % uds_pkt.sid)
			output.append(", content: %r" % uds_pkt.bin())
		else:
			output.append(" %r, content: %s" % (isotp_pkt.__class__.__name__, isotp_pkt.body_bytes))

		print("%s" % "".join(output))
