1# This file is part of Scapy 2# See http://www.secdev.org/projects/scapy for more information 3# Copyright (C) Nils Weiss <nils@we155.de> 4# This program is published under a GPLv2 license 5 6# scapy.contrib.description = CAN Calibration Protocol (CCP) 7# scapy.contrib.status = loads 8 9import struct 10 11from scapy.packet import Packet, bind_layers, bind_bottom_up 12from scapy.fields import XIntField, FlagsField, ByteEnumField, \ 13 ThreeBytesField, XBitField, ShortField, IntField, XShortField, \ 14 ByteField, XByteField, StrFixedLenField, LEShortField 15from scapy.layers.can import CAN 16 17 18class CCP(CAN): 19 name = 'CAN Calibration Protocol' 20 fields_desc = [ 21 FlagsField('flags', 0, 3, ['error', 22 'remote_transmission_request', 23 'extended']), 24 XBitField('identifier', 0, 29), 25 ByteField('length', 8), 26 ThreeBytesField('reserved', 0), 27 ] 28 29 def extract_padding(self, p): 30 return p, None 31 32 33class CRO(Packet): 34 commands = { 35 0x01: "CONNECT", 36 0x1B: "GET_CCP_VERSION", 37 0x17: "EXCHANGE_ID", 38 0x12: "GET_SEED", 39 0x13: "UNLOCK", 40 0x02: "SET_MTA", 41 0x03: "DNLOAD", 42 0x23: "DNLOAD_6", 43 0x04: "UPLOAD", 44 0x0F: "SHORT_UP", 45 0x11: "SELECT_CAL_PAGE", 46 0x14: "GET_DAQ_SIZE", 47 0x15: "SET_DAQ_PTR", 48 0x16: "WRITE_DAQ", 49 0x06: "START_STOP", 50 0x07: "DISCONNECT", 51 0x0C: "SET_S_STATUS", 52 0x0D: "GET_S_STATUS", 53 0x0E: "BUILD_CHKSUM", 54 0x10: "CLEAR_MEMORY", 55 0x18: "PROGRAM", 56 0x22: "PROGRAM_6", 57 0x19: "MOVE", 58 0x05: "TEST", 59 0x09: "GET_ACTIVE_CAL_PAGE", 60 0x08: "START_STOP_ALL", 61 0x20: "DIAG_SERVICE", 62 0x21: "ACTION_SERVICE" 63 } 64 name = 'Command Receive Object' 65 fields_desc = [ 66 ByteEnumField('cmd', 0x01, commands), 67 ByteField('ctr', 0) 68 ] 69 70 def hashret(self): 71 return struct.pack('B', self.ctr) 72 73 74# ##### CROs ###### 75 76class CONNECT(Packet): 77 fields_desc = [ 78 LEShortField('station_address', 0), 79 StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4), 80 ] 81 82 83bind_layers(CRO, CONNECT, cmd=0x01) 84 85 86class GET_CCP_VERSION(Packet): 87 fields_desc = [ 88 XByteField('main_protocol_version', 0), 89 XByteField('release_version', 0), 90 StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4) 91 ] 92 93 94bind_layers(CRO, GET_CCP_VERSION, cmd=0x1B) 95 96 97class EXCHANGE_ID(Packet): 98 fields_desc = [ 99 StrFixedLenField('ccp_master_device_id', b'\x00' * 6, length=6) 100 ] 101 102 103bind_layers(CRO, EXCHANGE_ID, cmd=0x17) 104 105 106class GET_SEED(Packet): 107 fields_desc = [ 108 XByteField('resource', 0), 109 StrFixedLenField('ccp_reserved', b'\xff' * 5, length=5) 110 ] 111 112 113bind_layers(CRO, GET_SEED, cmd=0x12) 114 115 116class UNLOCK(Packet): 117 fields_desc = [ 118 StrFixedLenField('key', b'\x00' * 6, length=6) 119 ] 120 121 122bind_layers(CRO, UNLOCK, cmd=0x13) 123 124 125class SET_MTA(Packet): 126 fields_desc = [ 127 XByteField('mta_num', 0), 128 XByteField('address_extension', 0), 129 XIntField('address', 0), 130 ] 131 132 133bind_layers(CRO, SET_MTA, cmd=0x02) 134 135 136class DNLOAD(Packet): 137 fields_desc = [ 138 XByteField('size', 0), 139 StrFixedLenField('data', b'\x00' * 5, length=5) 140 ] 141 142 143bind_layers(CRO, DNLOAD, cmd=0x03) 144 145 146class DNLOAD_6(Packet): 147 fields_desc = [ 148 StrFixedLenField('data', b'\x00' * 6, length=6) 149 ] 150 151 152bind_layers(CRO, DNLOAD_6, cmd=0x23) 153 154 155class UPLOAD(Packet): 156 fields_desc = [ 157 XByteField('size', 0), 158 StrFixedLenField('ccp_reserved', b'\xff' * 5, length=5) 159 ] 160 161 162bind_layers(CRO, UPLOAD, cmd=0x04) 163 164 165class SHORT_UP(Packet): 166 fields_desc = [ 167 XByteField('size', 0), 168 XByteField('address_extension', 0), 169 XIntField('address', 0), 170 ] 171 172 173bind_layers(CRO, SHORT_UP, cmd=0x0F) 174 175 176class SELECT_CAL_PAGE(Packet): 177 fields_desc = [ 178 StrFixedLenField('ccp_reserved', b'\xff' * 6, length=6) 179 ] 180 181 182bind_layers(CRO, SELECT_CAL_PAGE, cmd=0x11) 183 184 185class GET_DAQ_SIZE(Packet): 186 fields_desc = [ 187 XByteField('DAQ_num', 0), 188 XByteField('ccp_reserved', 0), 189 XIntField('DTO_identifier', 0), 190 ] 191 192 193bind_layers(CRO, GET_DAQ_SIZE, cmd=0x14) 194 195 196class SET_DAQ_PTR(Packet): 197 fields_desc = [ 198 XByteField('DAQ_num', 0), 199 XByteField('ODT_num', 0), 200 XByteField('ODT_element', 0), 201 StrFixedLenField('ccp_reserved', b'\xff' * 3, length=3) 202 ] 203 204 205bind_layers(CRO, SET_DAQ_PTR, cmd=0x15) 206 207 208class WRITE_DAQ(Packet): 209 fields_desc = [ 210 XByteField('DAQ_size', 0), 211 XByteField('address_extension', 0), 212 XIntField('address', 0), 213 ] 214 215 216bind_layers(CRO, WRITE_DAQ, cmd=0x16) 217 218 219class START_STOP(Packet): 220 fields_desc = [ 221 XByteField('mode', 0), 222 XByteField('DAQ_num', 0), 223 XByteField('ODT_num', 0), 224 XByteField('event_channel', 0), 225 XShortField('transmission_rate', 0), 226 ] 227 228 229bind_layers(CRO, START_STOP, cmd=0x06) 230 231 232class DISCONNECT(Packet): 233 fields_desc = [ 234 ByteEnumField('type', 0, {0: "temporary", 1: "end_of_session"}), 235 StrFixedLenField('ccp_reserved0', b'\xff' * 1, length=1), 236 LEShortField('station_address', 0), 237 StrFixedLenField('ccp_reserved', b'\xff' * 2, length=2) 238 ] 239 240 241bind_layers(CRO, DISCONNECT, cmd=0x07) 242 243 244class SET_S_STATUS(Packet): 245 name = "Set Session Status" 246 fields_desc = [ 247 FlagsField("session_status", 0, 8, ["CAL", "DAQ", "RESUME", "RES0", 248 "RES1", "RES2", "STORE", "RUN"]), 249 StrFixedLenField('ccp_reserved', b'\xff' * 5, length=5) 250 ] 251 252 253bind_layers(CRO, SET_S_STATUS, cmd=0x0C) 254 255 256class GET_S_STATUS(Packet): 257 fields_desc = [ 258 StrFixedLenField('ccp_reserved', b'\xff' * 6, length=6) 259 ] 260 261 262bind_layers(CRO, GET_S_STATUS, cmd=0x0D) 263 264 265class BUILD_CHKSUM(Packet): 266 fields_desc = [ 267 IntField('size', 0), 268 StrFixedLenField('ccp_reserved', b'\xff' * 2, length=2) 269 ] 270 271 272bind_layers(CRO, BUILD_CHKSUM, cmd=0x0E) 273 274 275class CLEAR_MEMORY(Packet): 276 fields_desc = [ 277 IntField('size', 0), 278 StrFixedLenField('ccp_reserved', b'\xff' * 2, length=2) 279 ] 280 281 282bind_layers(CRO, CLEAR_MEMORY, cmd=0x10) 283 284 285class PROGRAM(Packet): 286 fields_desc = [ 287 XByteField('size', 0), 288 StrFixedLenField('data', b'\x00' * 0, 289 length_from=lambda pkt: pkt.size), 290 StrFixedLenField('ccp_reserved', b'\xff' * 5, 291 length_from=lambda pkt: 5 - pkt.size) 292 ] 293 294 295bind_layers(CRO, PROGRAM, cmd=0x18) 296 297 298class PROGRAM_6(Packet): 299 fields_desc = [ 300 StrFixedLenField('data', b'\x00' * 6, length=6) 301 ] 302 303 304bind_layers(CRO, PROGRAM_6, cmd=0x22) 305 306 307class MOVE(Packet): 308 fields_desc = [ 309 IntField('size', 0), 310 StrFixedLenField('ccp_reserved', b'\xff' * 2, length=2) 311 ] 312 313 314bind_layers(CRO, MOVE, cmd=0x19) 315 316 317class TEST(Packet): 318 fields_desc = [ 319 LEShortField('station_address', 0), 320 StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4) 321 ] 322 323 324bind_layers(CRO, TEST, cmd=0x05) 325 326 327class GET_ACTIVE_CAL_PAGE(Packet): 328 fields_desc = [ 329 StrFixedLenField('ccp_reserved', b'\xff' * 6, length=6) 330 ] 331 332 333bind_layers(CRO, GET_ACTIVE_CAL_PAGE, cmd=0x09) 334 335 336class START_STOP_ALL(Packet): 337 fields_desc = [ 338 ByteEnumField('type', 0, {0: "stop", 1: "start"}), 339 StrFixedLenField('ccp_reserved', b'\xff' * 5, length=5) 340 341 ] 342 343 344bind_layers(CRO, START_STOP_ALL, cmd=0x08) 345 346 347class DIAG_SERVICE(Packet): 348 fields_desc = [ 349 ShortField('diag_service', 0), 350 StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4) 351 ] 352 353 354bind_layers(CRO, DIAG_SERVICE, cmd=0x20) 355 356 357class ACTION_SERVICE(Packet): 358 fields_desc = [ 359 ShortField('action_service', 0), 360 StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4) 361 ] 362 363 364bind_layers(CRO, ACTION_SERVICE, cmd=0x21) 365 366 367# ##### DTOs ###### 368 369class DEFAULT_DTO(Packet): 370 fields_desc = [ 371 StrFixedLenField('load', b'\xff' * 5, length=5), 372 ] 373 374 375class GET_CCP_VERSION_DTO(Packet): 376 fields_desc = [ 377 XByteField('main_protocol_version', 0), 378 XByteField('release_version', 0), 379 StrFixedLenField('ccp_reserved', b'\x00' * 3, length=3) 380 ] 381 382 383class EXCHANGE_ID_DTO(Packet): 384 fields_desc = [ 385 ByteField('slave_device_ID_length', 0), 386 ByteField('data_type_qualifier', 0), 387 ByteField('resource_availability_mask', 0), 388 ByteField('resource_protection_mask', 0), 389 StrFixedLenField('ccp_reserved', b'\xff' * 1, length=1), 390 ] 391 392 393class GET_SEED_DTO(Packet): 394 fields_desc = [ 395 XByteField('protection_status', 0), 396 StrFixedLenField('seed', b'\x00' * 4, length=4) 397 ] 398 399 400class UNLOCK_DTO(Packet): 401 fields_desc = [ 402 ByteField('privilege_status', 0), 403 StrFixedLenField('ccp_reserved', b'\xff' * 4, length=4), 404 ] 405 406 407class DNLOAD_DTO(Packet): 408 fields_desc = [ 409 XByteField('MTA0_extension', 0), 410 XIntField('MTA0_address', 0) 411 ] 412 413 414class DNLOAD_6_DTO(Packet): 415 fields_desc = [ 416 XByteField('MTA0_extension', 0), 417 XIntField('MTA0_address', 0) 418 ] 419 420 421class UPLOAD_DTO(Packet): 422 fields_desc = [ 423 StrFixedLenField('data', b'\x00' * 5, length=5) 424 ] 425 426 427class SHORT_UP_DTO(Packet): 428 fields_desc = [ 429 StrFixedLenField('data', b'\x00' * 5, length=5) 430 ] 431 432 433class GET_DAQ_SIZE_DTO(Packet): 434 fields_desc = [ 435 XByteField('DAQ_list_size', 0), 436 XByteField('first_pid', 0), 437 StrFixedLenField('ccp_reserved', b'\xff' * 3, length=3) 438 ] 439 440 441class GET_S_STATUS_DTO(Packet): 442 fields_desc = [ 443 FlagsField("session_status", 0, 8, ["CAL", "DAQ", "RESUME", "RES0", 444 "RES1", "RES2", "STORE", "RUN"]), 445 ByteField('information_qualifier', 0), 446 StrFixedLenField('information', b'\x00' * 3, length=3) 447 ] 448 449 450class BUILD_CHKSUM_DTO(Packet): 451 fields_desc = [ 452 ByteField('checksum_size', 0), 453 StrFixedLenField('checksum_data', b'\x00' * 4, 454 length_from=lambda pkt: pkt.checksum_size), 455 StrFixedLenField('ccp_reserved', b'\xff' * 0, 456 length_from=lambda pkt: 4 - pkt.checksum_size) 457 ] 458 459 460class PROGRAM_DTO(Packet): 461 fields_desc = [ 462 ByteField('MTA0_extension', 0), 463 XIntField('MTA0_address', 0) 464 ] 465 466 467class PROGRAM_6_DTO(Packet): 468 fields_desc = [ 469 ByteField('MTA0_extension', 0), 470 XIntField('MTA0_address', 0) 471 ] 472 473 474class GET_ACTIVE_CAL_PAGE_DTO(Packet): 475 fields_desc = [ 476 XByteField('address_extension', 0), 477 XIntField('address', 0) 478 ] 479 480 481class DIAG_SERVICE_DTO(Packet): 482 fields_desc = [ 483 ByteField('data_length', 0), 484 ByteField('data_type', 0), 485 StrFixedLenField('ccp_reserved', b'\xff' * 3, length=3) 486 ] 487 488 489class ACTION_SERVICE_DTO(Packet): 490 fields_desc = [ 491 ByteField('data_length', 0), 492 ByteField('data_type', 0), 493 StrFixedLenField('ccp_reserved', b'\xff' * 3, length=3) 494 ] 495 496 497class DTO(Packet): 498 __slots__ = Packet.__slots__ + ["payload_cls"] 499 500 return_codes = { 501 0x00: "acknowledge / no error", 502 0x01: "DAQ processor overload", 503 0x10: "command processor busy", 504 0x11: "DAQ processor busy", 505 0x12: "internal timeout", 506 0x18: "key request", 507 0x19: "session status request", 508 0x20: "cold start request", 509 0x21: "cal. data init. request", 510 0x22: "DAQ list init. request", 511 0x23: "code update request", 512 0x30: "unknown command", 513 0x31: "command syntax", 514 0x32: "parameter(s) out of range", 515 0x33: "access denied", 516 0x34: "overload", 517 0x35: "access locked", 518 0x36: "resource/function not available" 519 } 520 fields_desc = [ 521 XByteField("packet_id", 0xff), 522 ByteEnumField('return_code', 0x00, return_codes), 523 ByteField('ctr', 0) 524 ] 525 526 def __init__(self, *args, **kwargs): 527 self.payload_cls = DEFAULT_DTO 528 if "payload_cls" in kwargs: 529 self.payload_cls = kwargs["payload_cls"] 530 del kwargs["payload_cls"] 531 Packet.__init__(self, *args, **kwargs) 532 533 def __eq__(self, other): 534 return super(DTO, self).__eq__(other) and \ 535 self.payload_cls == other.payload_cls 536 537 def guess_payload_class(self, payload): 538 return self.payload_cls 539 540 @staticmethod 541 def get_dto_cls(cmd): 542 try: 543 return { 544 0x03: DNLOAD_DTO, 545 0x04: UPLOAD_DTO, 546 0x09: GET_ACTIVE_CAL_PAGE_DTO, 547 0x0D: GET_S_STATUS_DTO, 548 0x0E: BUILD_CHKSUM_DTO, 549 0x0F: SHORT_UP_DTO, 550 0x12: GET_SEED_DTO, 551 0x13: UNLOCK_DTO, 552 0x14: GET_DAQ_SIZE_DTO, 553 0x17: EXCHANGE_ID_DTO, 554 0x18: PROGRAM_DTO, 555 0x1B: GET_CCP_VERSION_DTO, 556 0x20: DIAG_SERVICE_DTO, 557 0x21: ACTION_SERVICE_DTO, 558 0x22: PROGRAM_6_DTO, 559 0x23: DNLOAD_6_DTO 560 }[cmd] 561 except KeyError: 562 return DEFAULT_DTO 563 564 def answers(self, other): 565 """In CCP, the payload of a DTO packet is dependent on the cmd field 566 of a corresponding CRO packet. Two packets correspond, if there 567 ctr field is equal. If answers detect the corresponding CRO, it will 568 interpret the payload of a DTO with the correct class. In CCP, there is 569 no other way, to determine the class of a DTO payload. Since answers is 570 called on sr and sr1, this modification of the original answers 571 implementation will give a better user experience. """ 572 if not hasattr(other, "ctr"): 573 return 0 574 if self.ctr != other.ctr: 575 return 0 576 if not hasattr(other, "cmd"): 577 return 0 578 579 new_pl_cls = self.get_dto_cls(other.cmd) 580 if self.payload_cls != new_pl_cls and \ 581 self.payload_cls == DEFAULT_DTO: 582 data = bytes(self.load) 583 self.remove_payload() 584 self.add_payload(new_pl_cls(data)) 585 self.payload_cls = new_pl_cls 586 return 1 587 588 def hashret(self): 589 return struct.pack('B', self.ctr) 590 591 592bind_bottom_up(CCP, DTO) 593