1# gmlan unit tests 2# 3# Type the following command to launch start the tests: 4# $ sudo bash test/run_tests -t test/gmlan.uts -F 5 6% gmlan unit tests 7 8+ Configuration of scapy 9= Load gmlan layer 10~ conf 11 12load_contrib("automotive.ecu", globals_dict=globals()) 13load_contrib("automotive.gm.gmlan", globals_dict=globals()) 14 15from scapy.contrib.automotive.gm.gmlan_ecu_states import * 16from scapy.contrib.automotive.gm.gmlan_logging import * 17 18+ Basic Packet Tests() 19= Set GMLAN ECU AddressingScheme 20 21conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 2 22assert conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] == 2 23 24= Craft Packet 25x = GMLAN(b'\x52\x02\x01\x16\x71\x00\x00\x0c\xaa\xbb') 26x.load == b'\x00\x0c\xaa\xbb' 27x.service == 0x52 28 29= Craft VIN Packet 30x = GMLAN(b'\x5a\x90'+ raw(b"WOOOJBF35W1042000")) 31x.load == b'WOOOJBF35W1042000' 32x.dataIdentifier == 0x90 33 34= Test Packet with ECU AddressingScheme2 35x = GMLAN()/GMLAN_RMBA(b'\x11\x22\x44\x22') 36x.memoryAddress == 0x1122 37x.memorySize == 0x4422 38 39= Test Packet GMLAN_RMBAPR with ECU AddressingScheme2 40y = GMLAN()/GMLAN_RMBAPR(b'\x11\x22\x44\x22') 41y.memoryAddress == 0x1122 42y.dataRecord == b'\x44\x22' 43y.answers(x) == True 44 45= Craft Packet with ECU AddressingScheme2 46x = GMLAN() / GMLAN_RMBA(b'\x11\x22\x44\x22') 47y = GMLAN()/GMLAN_RMBA(memoryAddress=0x1122, memorySize=0x4422) 48bytes(x) == bytes(y) 49 50= Test Packet with ECU AddressingScheme3 51conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 3 52x = GMLAN()/GMLAN_RMBA(b'\x11\x22\x44\x22\x11') 53x.memoryAddress == 0x112244 54x.memorySize == 0x2211 55 56= Test Packet GMLAN_RMBAPR with ECU AddressingScheme3 57y = GMLAN()/GMLAN_RMBAPR(b'\x11\x22\x44\x22\x11') 58y.memoryAddress == 0x112244 59y.dataRecord == b'\x22\x11' 60y.answers(x) == True 61 62= Craft Packet with ECU AddressingScheme3 63x = GMLAN() / GMLAN_RMBA(b'\x11\x22\x44\x22\x11') 64y = GMLAN()/GMLAN_RMBA(memoryAddress=0x112244, memorySize=0x2211) 65bytes(x) == bytes(y) 66 67= Test Packet with ECU AddressingScheme4 68conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 69x = GMLAN()/GMLAN_RMBA(b'\x11\x22\x44\x22\x11\x00') 70x.memoryAddress == 0x11224422 71x.memorySize == 0x1100 72 73= Test Packet GMLAN_RMBAPR with ECU AddressingScheme4 74y = GMLAN()/GMLAN_RMBAPR(b'\x11\x22\x44\x22\x11\x00') 75y.memoryAddress == 0x11224422 76y.dataRecord == b'\x11\x00' 77y.answers(x) == True 78 79= Craft Packet with ECU AddressingScheme4 80x = GMLAN() / GMLAN_RMBA(b'\x11\x22\x44\x22\x11\x00') 81y = GMLAN()/GMLAN_RMBA(memoryAddress=0x11224422, memorySize=0x1100) 82bytes(x) == bytes(y) 83 84= Craft Packet for RequestDownload2 85conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 2 86x = GMLAN(b'\x34\x12\x08\x15') 87x.service == 0x34 88x.dataFormatIdentifier == 0x12 89x.memorySize == 0x815 90 91y = GMLAN()/GMLAN_RD(dataFormatIdentifier=0x12, memorySize=0x815) 92bytes(y) == bytes(x) 93 94= Craft Packet for RequestDownload3 95conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 3 96x = GMLAN(b'\x34\x12\x08\x15\x00') 97x.service == 0x34 98x.dataFormatIdentifier == 0x12 99x.memorySize == 0x81500 100 101y = GMLAN()/GMLAN_RD(dataFormatIdentifier=0x12, memorySize=0x81500) 102bytes(y) == bytes(x) 103 104= Craft Packet for RequestDownload4 105conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 106x = GMLAN(b'\x34\x12\x08\x15\x00\x11') 107x.service == 0x34 108x.dataFormatIdentifier == 0x12 109x.memorySize == 0x8150011 110 111= Craft Packet for RFRD1 112a = GMLAN(b'\x12\x01') 113a.service == 0x12 114a.subfunction == 1 115 116= Craft Packet for RFRD2 117b = GMLAN(b'\x12\x02\x01\x02\x03\x04') 118b.service == 0x12 119b.subfunction == 2 120b.dtc.failureRecordNumber == 1 121b.dtc.DTCHighByte == 2 122b.dtc.DTCLowByte == 3 123b.dtc.DTCFailureType == 4 124 125= Craft Packet for RFRDPR_RFRI 126x = GMLAN(b'\x52\x01\x00\x01\x02\x03\x04') 127x.service == 0x52 128x.subfunction == 1 129x.failureRecordDataStructureIdentifier == 0 130x.dtcs[0].failureRecordNumber == 1 131x.dtcs[0].DTCHighByte == 2 132x.dtcs[0].DTCLowByte == 3 133x.dtcs[0].DTCFailureType == 4 134x.answers(a) == True 135 136= Craft Packet for RFRDPR_RFRI 137x = GMLAN(b'\x52\x01\x00\x01\x02\x03\x04\x01\x02\x03\x04\x01\x02\x03\x04\x01\x02\x03\x04') 138x.service == 0x52 139x.subfunction == 1 140x.failureRecordDataStructureIdentifier == 0 141x.dtcs[0].failureRecordNumber == 1 142x.dtcs[0].DTCHighByte == 2 143x.dtcs[0].DTCLowByte == 3 144x.dtcs[0].DTCFailureType == 4 145x.dtcs[1].failureRecordNumber == 1 146x.dtcs[1].DTCHighByte == 2 147x.dtcs[1].DTCLowByte == 3 148x.dtcs[1].DTCFailureType == 4 149x.dtcs[2].failureRecordNumber == 1 150x.dtcs[2].DTCHighByte == 2 151x.dtcs[2].DTCLowByte == 3 152x.dtcs[2].DTCFailureType == 4 153x.dtcs[3].failureRecordNumber == 1 154x.dtcs[3].DTCHighByte == 2 155x.dtcs[3].DTCLowByte == 3 156x.dtcs[3].DTCFailureType == 4 157x.answers(a) == True 158 159= Craft Packet for RFRDPR_RFRP 160x = GMLAN(b'\x52\x02\x01\x02\x03\x04deadbeef') 161x.service == 0x52 162x.subfunction == 2 163x.dtc.failureRecordNumber == 1 164x.dtc.DTCHighByte == 2 165x.dtc.DTCLowByte == 3 166x.dtc.DTCFailureType == 4 167x.show() 168x.load == b'deadbeef' 169x.answers(b) == True 170 171 172= Craft Packet for RDBI 173x = GMLAN(b'\x1A\x11') 174x.service == 0x1A 175x.dataIdentifier == 0x11 176 177= Craft Packet for RDBIPR 178y = GMLAN(b'\x5A\x11deadbeef') 179y.service == 0x5A 180y.dataIdentifier == 0x11 181y.load == b'deadbeef' 182y.answers(x) == True 183 184 185= Craft Packet for RDBPI 186x = GMLAN(b'\x22\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x66\x66\x77\x77\x88\x88\x99\x99') 187x.service == 0x22 188x.identifiers[0] == 0x1111 189x.identifiers[1] == 0x2222 190x.identifiers[2] == 0x3333 191x.identifiers[3] == 0x4444 192x.identifiers[4] == 0x5555 193x.identifiers[5] == 0x6666 194x.identifiers[6] == 0x7777 195x.identifiers[7] == 0x8888 196x.identifiers[8] == 0x9999 197 198= Craft Packet for RDBPIPR 199y = GMLAN(b'\x62\x11\x11deadbeef') 200y.service == 0x62 201y.parameterIdentifier == 0x1111 202y.load == b'deadbeef' 203y.answers(x) == True 204 205= Craft Packet for GMLAN_RDBPKTI1 206x = GMLAN(b'\xAA\x01deadbeef') 207x.service == 0xAA 208x.subfunction == 0x01 209x.request_DPIDs == [0x64, 0x65, 0x61, 0x64, 0x62, 0x65, 0x65, 0x66] 210 211= Craft Packet for GMLAN_RDBPKTI3 212x = GMLAN(b'\xAA\x02deadbeef') 213x.service == 0xAA 214x.subfunction == 0x02 215x.request_DPIDs == [0x64, 0x65, 0x61, 0x64, 0x62, 0x65, 0x65, 0x66] 216 217= Craft Packet for GMLAN_RDBPKTI4 218x = GMLAN(b'\xAA\x03deadbeef') 219x.service == 0xAA 220x.subfunction == 0x03 221x.request_DPIDs == [0x64, 0x65, 0x61, 0x64, 0x62, 0x65, 0x65, 0x66] 222 223= Craft Packet for GMLAN_RDBPKTI2 224x = GMLAN(b'\xAA\x00') 225x.service == 0xAA 226x.subfunction == 0 227 228= Build GMLAN_RDBPKTI1 229x = GMLAN()/GMLAN_RDBPKTI(subfunction=1, request_DPIDs=[0x64, 0x65]) 230assert b"\xaa\x01de" == bytes(x) 231 232= Craft Packet for GMLAN_SA1 233a = GMLAN(b'\x27\x01') 234a.service == 0x27 235a.subfunction == 1 236 237= Craft Packet for GMLAN_SA2 238b = GMLAN(b'\x27\x02\xde\xad') 239b.service == 0x27 240b.subfunction == 2 241b.securityKey == 0xdead 242 243= Craft Packet for GMLAN_SAPR1 244x = GMLAN(b'\x67\x02') 245x.service == 0x67 246x.subfunction == 2 247x.answers(b) 248 249ecu = Ecu() 250ecu.update(b) 251ecu.update(x) 252assert ecu.state.security_level == 2 253 254 255= Craft Packet for GMLAN_SAPR2 256x = GMLAN(b'\x67\x01\xde\xad') 257x.service == 0x67 258x.subfunction == 1 259x.securitySeed == 0xdead 260x.answers(a) 261 262= Craft Packet for GMLAN_DDM 263x = GMLAN(b'\x2c\x02dead') 264x.service == 0x2c 265x.DPIDIdentifier == 2 266x.PIDData == b'dead' 267 268= Craft Packet for GMLAN_DDMPR 269y = GMLAN(b'\x6c\x02dead') 270y.service == 0x6c 271y.DPIDIdentifier == 2 272y.answers(x) 273 274= Craft Packet for GMLAN_DPBA1 275conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 2 276x = GMLAN(b'\x2D\x02\x02\x11\x11\x33') 277x.service == 0x2d 278x.parameterIdentifier == 0x202 279x.memoryAddress == 0x1111 280x.memorySize == 0x33 281 282= Craft Packet for GMLAN_DPBA2 283conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 3 284x = GMLAN(b'\x2D\x02\x02\x11\x11\x11\x33') 285x.service == 0x2d 286x.parameterIdentifier == 0x202 287x.memoryAddress == 0x111111 288x.memorySize == 0x33 289 290= Craft Packet for GMLAN_DPBA3 291conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 292x = GMLAN(b'\x2D\x02\x02\x11\x11\x11\x11\x33') 293x.service == 0x2d 294x.parameterIdentifier == 0x202 295x.memoryAddress == 0x11111111 296x.memorySize == 0x33 297 298= Craft Packet for GMLAN_DPBAPR 299y = GMLAN(b'\x6D\x02\x02') 300y.service == 0x6d 301y.parameterIdentifier == 0x202 302y.answers(x) 303 304= Craft Packet for GMLAN_RD1 305conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 2 306x = GMLAN(b'\x34\x02\x11\x11') 307x.service == 0x34 308x.dataFormatIdentifier == 0x2 309x.memorySize == 0x1111 310 311= Craft Packet for GMLAN_RD2 312conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 3 313x = GMLAN(b'\x34\x02\x11\x11\x11') 314x.service == 0x34 315x.dataFormatIdentifier == 0x2 316x.memorySize == 0x111111 317 318= Craft Packet for GMLAN_RD3 319conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 320x = GMLAN(b'\x34\x02\x11\x11\x11\x11') 321x.service == 0x34 322x.dataFormatIdentifier == 0x2 323x.memorySize == 0x11111111 324 325= Craft Packet for GMLAN_TD1 326conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 2 327x = GMLAN(b'\x36\x02\x11\x11dead') 328x.service == 0x36 329x.subfunction == 0x2 330x.startingAddress == 0x1111 331x.dataRecord == b'dead' 332 333= Craft Packet for GMLAN_TD2 334conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 3 335x = GMLAN(b'\x36\x02\x11\x11\x11dead') 336x.service == 0x36 337x.subfunction == 0x2 338x.startingAddress == 0x111111 339x.dataRecord == b'dead' 340 341= Craft Packet for GMLAN_TD3 342conf.contribs['GMLAN']['GMLAN_ECU_AddressingScheme'] = 4 343x = GMLAN(b'\x36\x02\x11\x11\x11\x11dead') 344x.service == 0x36 345x.subfunction == 0x2 346x.startingAddress == 0x11111111 347x.dataRecord == b'dead' 348 349= Craft Packet for WDBI 350x = GMLAN(b'\x3b\x11deadbeef') 351x.service == 0x3b 352x.dataIdentifier == 0x11 353x.dataRecord == b'deadbeef' 354 355= Craft Packet for WDBIPR 356y = GMLAN(b'\x7b\x11') 357y.service == 0x7b 358y.dataIdentifier == 0x11 359y.answers(x) 360 361= Craft Packet for RPSPR 362x = GMLAN(b'\xe2\x11') 363x.service == 0xe2 364x.programmedState == 0x11 365 366= Craft Packet for PM 367x = GMLAN(b'\xA5\x11') 368x.service == 0xA5 369x.subfunction == 0x11 370 371= Craft Packet for RDI 372x = GMLAN(b'\xA9\x11') 373x.service == 0xA9 374x.subfunction == 0x11 375 376= Craft Packet for RDI_BN 377x = GMLAN(b'\xA9\x80\x11\x22\x33') 378x.service == 0xA9 379x.subfunction == 0x80 380x.DTCHighByte == 0x11 381x.DTCLowByte == 0x22 382x.DTCFailureType == 0x33 383 384= Craft Packet for RDI_BM1 385x = GMLAN(b'\xA9\x81\x11') 386x.service == 0xA9 387x.subfunction == 0x81 388x.DTCStatusMask == 0x11 389 390= Craft Packet for RDI_BM2 391x = GMLAN(b'\xA9\x82\x11') 392x.service == 0xA9 393x.subfunction == 0x82 394x.DTCStatusMask == 0x11 395 396= Craft Packet for NR 397x = GMLAN(b'\x7f\x11\x00\x11\x22') 398x.service == 0x7f 399x.requestServiceId == 0x11 400x.returnCode == 0 401x.deviceControlLimitExceeded == 0x1122 402 403= Check not answers 404y = GMLAN(b'\x11deadbeef') 405x = GMLAN(b'\x7f\x10\x00\x11\x22') 406assert not x.answers(y) 407 408= Check answers 1 409y = GMLAN(b'\x10deadbeef') 410x = GMLAN(b'\x7f\x10\x00\x11\x22') 411assert x.answers(y) 412 413= Set treat-response-pending-as-answer 414conf.contribs['GMLAN']['treat-response-pending-as-answer'] = False 415assert conf.contribs['GMLAN']['treat-response-pending-as-answer'] == False 416 417= Check response-pending is not considered as answer 418y = GMLAN(b'\x10deadbeef') 419x = GMLAN(b'\x7f\x10\x78\x11\x22') 420assert not x.answers(y) 421 422= Check response-pending is considered as answer 423conf.contribs['GMLAN']['treat-response-pending-as-answer'] = True 424assert conf.contribs['GMLAN']['treat-response-pending-as-answer'] == True 425y = GMLAN(b'\x10deadbeef') 426x = GMLAN(b'\x7f\x10\x78\x11\x22') 427assert x.answers(y) 428 429= Check hashret 1 430print(y.hashret()) 431print(x.hashret()) 432 433y.hashret() == x.hashret() 434 435= Check answers 2 436y = GMLAN()/GMLAN_SA(subfunction=1) 437x = GMLAN()/GMLAN_SAPR(subfunction=1) 438assert x.answers(y) 439 440= Check hashret 2 441y.hashret() == x.hashret() 442 443= Check modifies ecu state 444ecu = Ecu() 445ecu.update(GMLAN(service="InitiateDiagnosticOperation")) 446ecu.update(GMLAN(service="InitiateDiagnosticOperationPositiveResponse")) 447assert ecu.state.session == 3 448ecu.update(GMLAN(service="ReturnToNormalOperation")) 449ecu.update(GMLAN(service="ReturnToNormalOperationPositiveResponse")) 450assert ecu.state.session == 1 451ecu.update(GMLAN(service="ProgrammingMode")) 452ecu.update(GMLAN(service="ProgrammingModePositiveResponse")) 453assert ecu.state.session == 2 454ecu.update(GMLAN(service="DisableNormalCommunication")) 455ecu.update(GMLAN(service="DisableNormalCommunicationPositiveResponse")) 456assert ecu.state.communication_control == 1 457ecu.update(GMLAN(service="ReturnToNormalOperation")) 458ecu.update(GMLAN(service="ReturnToNormalOperationPositiveResponse")) 459assert ecu.state.session == 1 460 461= Craft GMLAN_DC 462 463req = GMLAN()/GMLAN_DC(CPIDNumber=0x11, CPIDControlBytes=b"\xbe\xefabc") 464assert bytes(req) == b"\xAE\x11\xbe\xefabc" 465 466req2 = GMLAN()/GMLAN_DC(CPIDNumber=0x12) 467assert bytes(req2) == b"\xAE\x12\x00\x00\x00\x00\x00" 468 469resp = GMLAN()/GMLAN_DCPR(CPIDNumber=0x11) 470assert bytes(resp) == b"\xEE\x11" 471 472 473assert resp.answers(req) 474assert not resp.answers(req2) 475 476= Dissect test GMLAN_DC 477 478req = GMLAN(b"\xAE\x14caffe") 479assert req.service == 0xAE 480assert req.CPIDNumber == 20 481assert req.CPIDControlBytes == b"caffe" 482 483resp = GMLAN(b"\xEE\x14") 484assert resp.service == 0xEE 485assert resp.CPIDNumber == 20 486assert resp.answers(req) 487assert resp.hashret() == req.hashret() 488 489= Logging tests 490 491 492def get_log(pkt): 493 for layer in pkt.layers(): 494 if not hasattr(layer, "get_log"): 495 continue 496 try: 497 return layer.get_log(pkt) 498 except TypeError: 499 return layer.get_log.im_func(pkt) 500 501pkt = GMLAN()/GMLAN_RFRD(subfunction=1) 502log = get_log(pkt) 503assert len(log) == 2 504assert log[1] == "readFailureRecordIdentifiers" 505assert log[0] == "ReadFailureRecordData" 506 507pkt = GMLAN()/GMLAN_RFRDPR(subfunction=1) 508log = get_log(pkt) 509assert len(log) == 2 510assert log[1] == "readFailureRecordIdentifiers" 511assert log[0] == "ReadFailureRecordDataPositiveResponse" 512 513pkt = GMLAN()/GMLAN_RDBPI(identifiers=[5]) 514log = get_log(pkt) 515print(log) 516assert len(log) == 2 517assert log[1] == '[OBD_EngineCoolantTemperature]' 518assert log[0] == "ReadDataByParameterIdentifier" 519 520pkt = GMLAN()/GMLAN_RDBPIPR(parameterIdentifier=5) 521log = get_log(pkt) 522print(log) 523assert len(log) == 2 524assert log[1] == 'OBD_EngineCoolantTemperature' 525assert log[0] == "ReadDataByParameterIdentifierPositiveResponse" 526 527 528pkt = GMLAN()/GMLAN_RDBPKTI(subfunction=0) 529log = get_log(pkt) 530print(log) 531assert len(log) == 2 532assert log[1] == 'stopSending' 533assert log[0] == "ReadDataByPacketIdentifier" 534 535pkt = GMLAN()/GMLAN_RMBA(memoryAddress=0) 536log = get_log(pkt) 537print(log) 538assert len(log) == 2 539assert log[1] == '0x0' 540assert log[0] == "ReadMemoryByAddress" 541 542pkt = GMLAN()/GMLAN_RMBAPR(memoryAddress=0, dataRecord=b"deadbeef") 543log = get_log(pkt) 544print(log) 545assert len(log) == 2 546assert log[1][0] == '0x0' 547assert log[1][1] == b'deadbeef' 548assert log[0] == "ReadMemoryByAddressPositiveResponse" 549 550pkt = GMLAN()/GMLAN_DDM(DPIDIdentifier=0, PIDData=b"deadbeef") 551log = get_log(pkt) 552print(log) 553assert len(log) == 2 554assert log[1][0] == '0x0' 555assert log[1][1] == b'deadbeef' 556assert log[0] == "DynamicallyDefineMessage" 557 558pkt = GMLAN()/GMLAN_DDMPR(DPIDIdentifier=0) 559log = get_log(pkt) 560print(log) 561assert len(log) == 2 562assert log[1] == '0x0' 563assert log[0] == "DynamicallyDefineMessagePositiveResponse" 564 565pkt = GMLAN()/GMLAN_DPBA(parameterIdentifier=0, memoryAddress=1, memorySize=3) 566log = get_log(pkt) 567print(log) 568assert len(log) == 2 569assert log[1][0] == 0 570assert log[1][1] == 1 571assert log[1][2] == 3 572assert log[0] == "DefinePIDByAddress" 573 574pkt = GMLAN()/GMLAN_DPBAPR(parameterIdentifier=0) 575log = get_log(pkt) 576print(log) 577assert len(log) == 2 578assert log[1] == 0 579assert log[0] == "DefinePIDByAddressPositiveResponse" 580 581pkt = GMLAN()/GMLAN_WDBI(dataIdentifier=0, dataRecord=b"deadbeef") 582log = get_log(pkt) 583print(log) 584assert len(log) == 2 585assert log[1][0] == "0x0" 586assert log[1][1] == b"deadbeef" 587assert log[0] == "WriteDataByIdentifier" 588 589pkt = GMLAN()/GMLAN_WDBIPR(dataIdentifier=0) 590log = get_log(pkt) 591print(log) 592assert len(log) == 2 593assert log[1] == "0x0" 594assert log[0] == "WriteDataByIdentifierPositiveResponse" 595 596pkt = GMLAN()/GMLAN_RDI(subfunction=0x80) 597log = get_log(pkt) 598print(log) 599assert len(log) == 2 600assert log[1] == "readStatusOfDTCByDTCNumber" 601assert log[0] == "ReadDiagnosticInformation" 602 603pkt = GMLAN()/GMLAN_DC(CPIDNumber=0x80) 604log = get_log(pkt) 605print(log) 606assert len(log) == 2 607assert log[1] == "0x80" 608assert log[0] == "DeviceControl" 609 610pkt = GMLAN()/GMLAN_DCPR(CPIDNumber=0x80) 611log = get_log(pkt) 612print(log) 613assert len(log) == 2 614assert log[1] == "0x80" 615assert log[0] == "DeviceControlPositiveResponse"