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