86890704fd
todo: add documentation & wireshark dissector
588 lines
14 KiB
Python
Executable file
588 lines
14 KiB
Python
Executable file
# This file is part of Scapy
|
|
# See http://www.secdev.org/projects/scapy for more information
|
|
# Copyright (C) Nils Weiss <nils@we155.de>
|
|
# This program is published under a GPLv2 license
|
|
|
|
# scapy.contrib.description = CAN Calibration Protocol (CCP)
|
|
# scapy.contrib.status = loads
|
|
|
|
import struct
|
|
|
|
from scapy.packet import Packet, bind_layers, bind_bottom_up
|
|
from scapy.fields import XIntField, FlagsField, ByteEnumField, \
|
|
ThreeBytesField, XBitField, ShortField, IntField, XShortField, \
|
|
ByteField, XByteField, StrFixedLenField, LEShortField
|
|
from scapy.layers.can import CAN
|
|
|
|
|
|
class CCP(CAN):
|
|
name = 'CAN Calibration Protocol'
|
|
fields_desc = [
|
|
FlagsField('flags', 0, 3, ['error',
|
|
'remote_transmission_request',
|
|
'extended']),
|
|
XBitField('identifier', 0, 29),
|
|
ByteField('length', 8),
|
|
ThreeBytesField('reserved', 0),
|
|
]
|
|
|
|
def extract_padding(self, p):
|
|
return p, None
|
|
|
|
|
|
class CRO(Packet):
|
|
commands = {
|
|
0x01: "CONNECT",
|
|
0x1B: "GET_CCP_VERSION",
|
|
0x17: "EXCHANGE_ID",
|
|
0x12: "GET_SEED",
|
|
0x13: "UNLOCK",
|
|
0x02: "SET_MTA",
|
|
0x03: "DNLOAD",
|
|
0x23: "DNLOAD_6",
|
|
0x04: "UPLOAD",
|
|
0x0F: "SHORT_UP",
|
|
0x11: "SELECT_CAL_PAGE",
|
|
0x14: "GET_DAQ_SIZE",
|
|
0x15: "SET_DAQ_PTR",
|
|
0x16: "WRITE_DAQ",
|
|
0x06: "START_STOP",
|
|
0x07: "DISCONNECT",
|
|
0x0C: "SET_S_STATUS",
|
|
0x0D: "GET_S_STATUS",
|
|
0x0E: "BUILD_CHKSUM",
|
|
0x10: "CLEAR_MEMORY",
|
|
0x18: "PROGRAM",
|
|
0x22: "PROGRAM_6",
|
|
0x19: "MOVE",
|
|
0x05: "TEST",
|
|
0x09: "GET_ACTIVE_CAL_PAGE",
|
|
0x08: "START_STOP_ALL",
|
|
0x20: "DIAG_SERVICE",
|
|
0x21: "ACTION_SERVICE"
|
|
}
|
|
name = 'Command Receive Object'
|
|
fields_desc = [
|
|
ByteEnumField('cmd', 0x01, commands),
|
|
ByteField('ctr', 0)
|
|
]
|
|
|
|
def hashret(self):
|
|
return struct.pack('B', self.ctr)
|
|
|
|
|
|
# ##### CROs ######
|
|
|
|
class CONNECT(Packet):
|
|
fields_desc = [
|
|
LEShortField('station_address', 0),
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4),
|
|
]
|
|
|
|
|
|
bind_layers(CRO, CONNECT, cmd=0x01)
|
|
|
|
|
|
class GET_CCP_VERSION(Packet):
|
|
fields_desc = [
|
|
XByteField('main_protocol_version', 0),
|
|
XByteField('release_version', 0),
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4)
|
|
]
|
|
|
|
|
|
bind_layers(CRO, GET_CCP_VERSION, cmd=0x1B)
|
|
|
|
|
|
class EXCHANGE_ID(Packet):
|
|
fields_desc = [
|
|
StrFixedLenField('ccp_master_device_id', b'\x00' * 6, length=6)
|
|
]
|
|
|
|
|
|
bind_layers(CRO, EXCHANGE_ID, cmd=0x17)
|
|
|
|
|
|
class GET_SEED(Packet):
|
|
fields_desc = [
|
|
XByteField('resource', 0),
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 5, length=5)
|
|
]
|
|
|
|
|
|
bind_layers(CRO, GET_SEED, cmd=0x12)
|
|
|
|
|
|
class UNLOCK(Packet):
|
|
fields_desc = [
|
|
StrFixedLenField('key', b'\x00' * 6, length=6)
|
|
]
|
|
|
|
|
|
bind_layers(CRO, UNLOCK, cmd=0x13)
|
|
|
|
|
|
class SET_MTA(Packet):
|
|
fields_desc = [
|
|
XByteField('mta_num', 0),
|
|
XByteField('address_extension', 0),
|
|
XIntField('address', 0),
|
|
]
|
|
|
|
|
|
bind_layers(CRO, SET_MTA, cmd=0x02)
|
|
|
|
|
|
class DNLOAD(Packet):
|
|
fields_desc = [
|
|
XByteField('size', 0),
|
|
StrFixedLenField('data', b'\x00' * 5, length=5)
|
|
]
|
|
|
|
|
|
bind_layers(CRO, DNLOAD, cmd=0x03)
|
|
|
|
|
|
class DNLOAD_6(Packet):
|
|
fields_desc = [
|
|
StrFixedLenField('data', b'\x00' * 6, length=6)
|
|
]
|
|
|
|
|
|
bind_layers(CRO, DNLOAD_6, cmd=0x23)
|
|
|
|
|
|
class UPLOAD(Packet):
|
|
fields_desc = [
|
|
XByteField('size', 0),
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 5, length=5)
|
|
]
|
|
|
|
|
|
bind_layers(CRO, UPLOAD, cmd=0x04)
|
|
|
|
|
|
class SHORT_UP(Packet):
|
|
fields_desc = [
|
|
XByteField('size', 0),
|
|
XByteField('address_extension', 0),
|
|
XIntField('address', 0),
|
|
]
|
|
|
|
|
|
bind_layers(CRO, SHORT_UP, cmd=0x0F)
|
|
|
|
|
|
class SELECT_CAL_PAGE(Packet):
|
|
fields_desc = [
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 6, length=6)
|
|
]
|
|
|
|
|
|
bind_layers(CRO, SELECT_CAL_PAGE, cmd=0x11)
|
|
|
|
|
|
class GET_DAQ_SIZE(Packet):
|
|
fields_desc = [
|
|
XByteField('DAQ_num', 0),
|
|
XByteField('ccp_reserved', 0),
|
|
XIntField('DTO_identifier', 0),
|
|
]
|
|
|
|
|
|
bind_layers(CRO, GET_DAQ_SIZE, cmd=0x14)
|
|
|
|
|
|
class SET_DAQ_PTR(Packet):
|
|
fields_desc = [
|
|
XByteField('DAQ_num', 0),
|
|
XByteField('ODT_num', 0),
|
|
XByteField('ODT_element', 0),
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 3, length=3)
|
|
]
|
|
|
|
|
|
bind_layers(CRO, SET_DAQ_PTR, cmd=0x15)
|
|
|
|
|
|
class WRITE_DAQ(Packet):
|
|
fields_desc = [
|
|
XByteField('DAQ_size', 0),
|
|
XByteField('address_extension', 0),
|
|
XIntField('address', 0),
|
|
]
|
|
|
|
|
|
bind_layers(CRO, WRITE_DAQ, cmd=0x16)
|
|
|
|
|
|
class START_STOP(Packet):
|
|
fields_desc = [
|
|
XByteField('mode', 0),
|
|
XByteField('DAQ_num', 0),
|
|
XByteField('ODT_num', 0),
|
|
XByteField('event_channel', 0),
|
|
XShortField('transmission_rate', 0),
|
|
]
|
|
|
|
|
|
bind_layers(CRO, START_STOP, cmd=0x06)
|
|
|
|
|
|
class DISCONNECT(Packet):
|
|
fields_desc = [
|
|
ByteEnumField('type', 0, {0: "temporary", 1: "end_of_session"}),
|
|
StrFixedLenField('ccp_reserved0', b'\xff' * 1, length=1),
|
|
LEShortField('station_address', 0),
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 2, length=2)
|
|
]
|
|
|
|
|
|
bind_layers(CRO, DISCONNECT, cmd=0x07)
|
|
|
|
|
|
class SET_S_STATUS(Packet):
|
|
name = "Set Session Status"
|
|
fields_desc = [
|
|
FlagsField("session_status", 0, 8, ["CAL", "DAQ", "RESUME", "RES0",
|
|
"RES1", "RES2", "STORE", "RUN"]),
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 5, length=5)
|
|
]
|
|
|
|
|
|
bind_layers(CRO, SET_S_STATUS, cmd=0x0C)
|
|
|
|
|
|
class GET_S_STATUS(Packet):
|
|
fields_desc = [
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 6, length=6)
|
|
]
|
|
|
|
|
|
bind_layers(CRO, GET_S_STATUS, cmd=0x0D)
|
|
|
|
|
|
class BUILD_CHKSUM(Packet):
|
|
fields_desc = [
|
|
IntField('size', 0),
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 2, length=2)
|
|
]
|
|
|
|
|
|
bind_layers(CRO, BUILD_CHKSUM, cmd=0x0E)
|
|
|
|
|
|
class CLEAR_MEMORY(Packet):
|
|
fields_desc = [
|
|
IntField('size', 0),
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 2, length=2)
|
|
]
|
|
|
|
|
|
bind_layers(CRO, CLEAR_MEMORY, cmd=0x10)
|
|
|
|
|
|
class PROGRAM(Packet):
|
|
fields_desc = [
|
|
XByteField('size', 0),
|
|
StrFixedLenField('data', b'\x00' * 0,
|
|
length_from=lambda pkt: pkt.size),
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 5,
|
|
length_from=lambda pkt: 5 - pkt.size)
|
|
]
|
|
|
|
|
|
bind_layers(CRO, PROGRAM, cmd=0x18)
|
|
|
|
|
|
class PROGRAM_6(Packet):
|
|
fields_desc = [
|
|
StrFixedLenField('data', b'\x00' * 6, length=6)
|
|
]
|
|
|
|
|
|
bind_layers(CRO, PROGRAM_6, cmd=0x22)
|
|
|
|
|
|
class MOVE(Packet):
|
|
fields_desc = [
|
|
IntField('size', 0),
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 2, length=2)
|
|
]
|
|
|
|
|
|
bind_layers(CRO, MOVE, cmd=0x19)
|
|
|
|
|
|
class TEST(Packet):
|
|
fields_desc = [
|
|
LEShortField('station_address', 0),
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4)
|
|
]
|
|
|
|
|
|
bind_layers(CRO, TEST, cmd=0x05)
|
|
|
|
|
|
class GET_ACTIVE_CAL_PAGE(Packet):
|
|
fields_desc = [
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 6, length=6)
|
|
]
|
|
|
|
|
|
bind_layers(CRO, GET_ACTIVE_CAL_PAGE, cmd=0x09)
|
|
|
|
|
|
class START_STOP_ALL(Packet):
|
|
fields_desc = [
|
|
ByteEnumField('type', 0, {0: "stop", 1: "start"}),
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 5, length=5)
|
|
|
|
]
|
|
|
|
|
|
bind_layers(CRO, START_STOP_ALL, cmd=0x08)
|
|
|
|
|
|
class DIAG_SERVICE(Packet):
|
|
fields_desc = [
|
|
ShortField('diag_service', 0),
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4)
|
|
]
|
|
|
|
|
|
bind_layers(CRO, DIAG_SERVICE, cmd=0x20)
|
|
|
|
|
|
class ACTION_SERVICE(Packet):
|
|
fields_desc = [
|
|
ShortField('action_service', 0),
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4)
|
|
]
|
|
|
|
|
|
bind_layers(CRO, ACTION_SERVICE, cmd=0x21)
|
|
|
|
|
|
# ##### DTOs ######
|
|
|
|
class DEFAULT_DTO(Packet):
|
|
fields_desc = [
|
|
StrFixedLenField('load', b'\xff' * 5, length=5),
|
|
]
|
|
|
|
|
|
class GET_CCP_VERSION_DTO(Packet):
|
|
fields_desc = [
|
|
XByteField('main_protocol_version', 0),
|
|
XByteField('release_version', 0),
|
|
StrFixedLenField('ccp_reserved', b'\x00' * 3, length=3)
|
|
]
|
|
|
|
|
|
class EXCHANGE_ID_DTO(Packet):
|
|
fields_desc = [
|
|
ByteField('slave_device_ID_length', 0),
|
|
ByteField('data_type_qualifier', 0),
|
|
ByteField('resource_availability_mask', 0),
|
|
ByteField('resource_protection_mask', 0),
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 1, length=1),
|
|
]
|
|
|
|
|
|
class GET_SEED_DTO(Packet):
|
|
fields_desc = [
|
|
XByteField('protection_status', 0),
|
|
StrFixedLenField('seed', b'\x00' * 4, length=4)
|
|
]
|
|
|
|
|
|
class UNLOCK_DTO(Packet):
|
|
fields_desc = [
|
|
ByteField('privilege_status', 0),
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4),
|
|
]
|
|
|
|
|
|
class DNLOAD_DTO(Packet):
|
|
fields_desc = [
|
|
XByteField('MTA0_extension', 0),
|
|
XIntField('MTA0_address', 0)
|
|
]
|
|
|
|
|
|
class DNLOAD_6_DTO(Packet):
|
|
fields_desc = [
|
|
XByteField('MTA0_extension', 0),
|
|
XIntField('MTA0_address', 0)
|
|
]
|
|
|
|
|
|
class UPLOAD_DTO(Packet):
|
|
fields_desc = [
|
|
StrFixedLenField('data', b'\x00' * 5, length=5)
|
|
]
|
|
|
|
|
|
class SHORT_UP_DTO(Packet):
|
|
fields_desc = [
|
|
StrFixedLenField('data', b'\x00' * 5, length=5)
|
|
]
|
|
|
|
|
|
class GET_DAQ_SIZE_DTO(Packet):
|
|
fields_desc = [
|
|
XByteField('DAQ_list_size', 0),
|
|
XByteField('first_pid', 0),
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 3, length=3)
|
|
]
|
|
|
|
|
|
class GET_S_STATUS_DTO(Packet):
|
|
fields_desc = [
|
|
FlagsField("session_status", 0, 8, ["CAL", "DAQ", "RESUME", "RES0",
|
|
"RES1", "RES2", "STORE", "RUN"]),
|
|
ByteField('information_qualifier', 0),
|
|
StrFixedLenField('information', b'\x00' * 3, length=3)
|
|
]
|
|
|
|
|
|
class BUILD_CHKSUM_DTO(Packet):
|
|
fields_desc = [
|
|
ByteField('checksum_size', 0),
|
|
StrFixedLenField('checksum_data', b'\x00' * 4,
|
|
length_from=lambda pkt: pkt.checksum_size),
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 0,
|
|
length_from=lambda pkt: 4 - pkt.checksum_size)
|
|
]
|
|
|
|
|
|
class PROGRAM_DTO(Packet):
|
|
fields_desc = [
|
|
ByteField('MTA0_extension', 0),
|
|
XIntField('MTA0_address', 0)
|
|
]
|
|
|
|
|
|
class PROGRAM_6_DTO(Packet):
|
|
fields_desc = [
|
|
ByteField('MTA0_extension', 0),
|
|
XIntField('MTA0_address', 0)
|
|
]
|
|
|
|
|
|
class GET_ACTIVE_CAL_PAGE_DTO(Packet):
|
|
fields_desc = [
|
|
XByteField('address_extension', 0),
|
|
XIntField('address', 0)
|
|
]
|
|
|
|
|
|
class DIAG_SERVICE_DTO(Packet):
|
|
fields_desc = [
|
|
ByteField('data_length', 0),
|
|
ByteField('data_type', 0),
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 3, length=3)
|
|
]
|
|
|
|
|
|
class ACTION_SERVICE_DTO(Packet):
|
|
fields_desc = [
|
|
ByteField('data_length', 0),
|
|
ByteField('data_type', 0),
|
|
StrFixedLenField('ccp_reserved', b'\xff' * 3, length=3)
|
|
]
|
|
|
|
|
|
class DTO(Packet):
|
|
__slots__ = Packet.__slots__ + ["payload_cls"]
|
|
|
|
return_codes = {
|
|
0x00: "acknowledge / no error",
|
|
0x01: "DAQ processor overload",
|
|
0x10: "command processor busy",
|
|
0x11: "DAQ processor busy",
|
|
0x12: "internal timeout",
|
|
0x18: "key request",
|
|
0x19: "session status request",
|
|
0x20: "cold start request",
|
|
0x21: "cal. data init. request",
|
|
0x22: "DAQ list init. request",
|
|
0x23: "code update request",
|
|
0x30: "unknown command",
|
|
0x31: "command syntax",
|
|
0x32: "parameter(s) out of range",
|
|
0x33: "access denied",
|
|
0x34: "overload",
|
|
0x35: "access locked",
|
|
0x36: "resource/function not available"
|
|
}
|
|
fields_desc = [
|
|
XByteField("packet_id", 0xff),
|
|
ByteEnumField('return_code', 0x00, return_codes),
|
|
ByteField('ctr', 0)
|
|
]
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.payload_cls = DEFAULT_DTO
|
|
if "payload_cls" in kwargs:
|
|
self.payload_cls = kwargs["payload_cls"]
|
|
del kwargs["payload_cls"]
|
|
Packet.__init__(self, *args, **kwargs)
|
|
|
|
def guess_payload_class(self, payload):
|
|
return self.payload_cls
|
|
|
|
@staticmethod
|
|
def get_dto_cls(cmd):
|
|
try:
|
|
return {
|
|
0x03: DNLOAD_DTO,
|
|
0x04: UPLOAD_DTO,
|
|
0x09: GET_ACTIVE_CAL_PAGE_DTO,
|
|
0x0D: GET_S_STATUS_DTO,
|
|
0x0E: BUILD_CHKSUM_DTO,
|
|
0x0F: SHORT_UP_DTO,
|
|
0x12: GET_SEED_DTO,
|
|
0x13: UNLOCK_DTO,
|
|
0x14: GET_DAQ_SIZE_DTO,
|
|
0x17: EXCHANGE_ID_DTO,
|
|
0x18: PROGRAM_DTO,
|
|
0x1B: GET_CCP_VERSION_DTO,
|
|
0x20: DIAG_SERVICE_DTO,
|
|
0x21: ACTION_SERVICE_DTO,
|
|
0x22: PROGRAM_6_DTO,
|
|
0x23: DNLOAD_6_DTO
|
|
}[cmd]
|
|
except KeyError:
|
|
return DEFAULT_DTO
|
|
|
|
def answers(self, other):
|
|
"""In CCP, the payload of a DTO packet is dependent on the cmd field
|
|
of a corresponding CRO packet. Two packets correspond, if there
|
|
ctr field is equal. If answers detect the corresponding CRO, it will
|
|
interpret the payload of a DTO with the correct class. In CCP, there is
|
|
no other way, to determine the class of a DTO payload. Since answers is
|
|
called on sr and sr1, this modification of the original answers
|
|
implementation will give a better user experience. """
|
|
if not hasattr(other, "ctr"):
|
|
return 0
|
|
if self.ctr != other.ctr:
|
|
return 0
|
|
if not hasattr(other, "cmd"):
|
|
return 0
|
|
|
|
new_pl_cls = self.get_dto_cls(other.cmd)
|
|
if self.payload_cls != new_pl_cls and \
|
|
self.payload_cls == DEFAULT_DTO:
|
|
data = bytes(self.load)
|
|
self.remove_payload()
|
|
self.add_payload(new_pl_cls(data))
|
|
self.payload_cls = new_pl_cls
|
|
return 1
|
|
|
|
def hashret(self):
|
|
return struct.pack('B', self.ctr)
|
|
|
|
|
|
bind_bottom_up(CCP, DTO)
|