From f834210391b0549770947019c8bf6b5c5034d04c Mon Sep 17 00:00:00 2001
From: Jacek Kowalski <Jacek@jacekk.info>
Date: Fri, 24 Jul 2020 19:35:18 +0000
Subject: [PATCH] ControlVault 3 - initial support

---
 /dev/null       |  189 -----------
 cv3.py          |   43 ++
 traffic_cv2.txt |    0 
 nfc.py          |   76 ++++
 traffic_cv3.txt |  529 +++++++++++++++++++++++++++++++
 cv2.py          |   46 ++
 cvcomm.py       |   93 +++++
 README.md       |   16 
 8 files changed, 796 insertions(+), 196 deletions(-)

diff --git a/README.md b/README.md
index 83c0e71..659ed04 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Enable NFC on Dell ControlVault2
+# Enable NFC on Dell ControlVault2 and ControlVault3
 
 ## Introduction
 
@@ -14,7 +14,7 @@
 
 1. Clone the repository.
 1. Install python3 and python3-usb.
-1. Run: `./bcm20795.py on` (use `sudo` if necessary).
+1. Run: `./nfc.py on` (use `sudo` if necessary).
 1. Run `pcsc_scan` or whatever you prefer.
 1. Enjoy!
 
@@ -24,8 +24,9 @@
 
 Currently only the following devices were tested and are known to work:
 
-* `0a5c:5832`
-* `0a5c:5834`
+* `0a5c:5832` (ControlVault 2),
+* `0a5c:5834` (ControlVault 2),
+* `0a5c:5843` (ControlVault 3).
 
 Firmware update (done during driver installation on Windows) may be required.
 
@@ -36,19 +37,20 @@
 * Dell Latitude 7280
 * Dell Latitude 7290
 * Dell Latitude 7390
+* Dell Latitude 7400
 * Dell Latitude E5270
 * Dell Latitude E7470
 * Dell Latitude E7490
 
 ## How it works?
 
-Python script sends the same sequence of commands the Windows driver does. The traffic was sniffed using USBPcap and Wireshark (kudos to [~jkramarz](https://github.com/jkramarz) for that).
+Python script sends the same sequence of commands the Windows driver does. The traffic was sniffed using USBPcap and Wireshark (kudos to [~jkramarz](https://github.com/jkramarz) and [~lgarbarini](https://github.com/lgarbarini) and for that).
 
 The data is sent as-is and responses are read, but no error-checking is done.
 
-The semi-annotated traffic dump is available as [traffic.txt](traffic.txt) - feel free to decode it further!
+The semi-annotated traffic dumps are available as [traffic_cv2.txt](traffic_cv2.txt) and [traffic_cv3.txt](traffic_cv3.txt) - feel free to decode it further!
 
-The communication protocol is based on NCI (NFC Controller Interface). Unfortunately the specs are not freely available and some proprietary extensions are used. libnfc-nci and kernel sources were used to decode some structs.
+The communication protocol is based on NCI (NFC Controller Interface). Unfortunately the specs are not freely available and some proprietary extensions are used. libnfc-nci and kernel sources were used to decode vendor-independent structs.
 
 ## References
 
diff --git a/bcm20795.py b/bcm20795.py
deleted file mode 100755
index c683016..0000000
--- a/bcm20795.py
+++ /dev/null
@@ -1,189 +0,0 @@
-#!/usr/bin/env python3
-
-# Enable NFC on Linux (CCID/PCSCD)
-# Dell E7470
-# Dell ControlVault2
-# BCM20795 (20795A1)
-
-import binascii
-import logging
-import math
-import struct
-import sys
-import time
-import usb.core
-import usb.util
-
-SUPPORTED_DEVICES = [
-	{'idVendor': 0x0A5C, 'idProduct': 0x5834},
-	{'idVendor': 0x0A5C, 'idProduct': 0x5832},
-]
-
-logging.basicConfig(level=logging.DEBUG)
-logger = logging.getLogger(__name__)
-
-def to_hex(val):
-	return ' '.join([bytes([i]).hex() for i in val])
-
-class BcmCommunicator:
-	def __init__(self, device, bulk_in, bulk_out):
-		self.logger = logging.getLogger(__name__)
-		self.device = device
-		self.bulk_in = bulk_in
-		self.bulk_out = bulk_out
-	
-	def ctrl_transfer(self, *args, **kwargs):
-		self.logger.debug('Control: {} {}'.format(args, kwargs))
-		return self.device.ctrl_transfer(*args, **kwargs)
-	
-	def write(self, *args, **kwargs):
-		return self.bulk_out.write(*args, **kwargs)
-	
-	def read(self, *args, **kwargs):
-		return self.bulk_in.read(*args, **kwargs)
-	
-	def send_packet(self, payload):
-		packet_type = 0x01
-		unknown1 = 0x00
-		length = len(payload)
-		
-		packet = struct.pack('>BBH', packet_type, unknown1, length) + payload
-		
-		self.logger.debug('Put: {}'.format(to_hex(packet)))
-		self.write(packet)
-
-	def recv_packet(self):
-		packet = self.read(64, timeout=5000).tobytes()
-		tag = packet[0:2]
-		if tag != b'\x00\x00':
-			raise Exception('Unknown tag: {}'.format(tag.hex()))
-		length = packet[2:4]
-		length = struct.unpack('>H', length)[0]
-		
-		for i in range(0, math.ceil(length/64) - 1):
-			packet += self.read(64).tobytes()
-		
-		self.logger.debug('Got: {}'.format(to_hex(packet)))
-		return packet[4:]
-	
-	def talk(self, exchange):
-		for packet in exchange:
-			self.send_packet(bytes.fromhex(packet))
-			data = self.recv_packet()
-			
-			if data[1] == 0x61:
-				packet = self.recv_packet()
-	
-	@staticmethod
-	def _dev_match(template, candidate):
-		for prop, value in template.items():
-			if prop not in candidate.__dict__ or candidate.__dict__[prop] != value:
-				return False
-		return True
-	
-	@classmethod
-	def _dev_matcher(cls, dev):
-		for device in SUPPORTED_DEVICES:
-			if cls._dev_match(device, dev):
-				return True
-		return False
-	
-	@classmethod
-	def find(cls):
-		logger = logging.getLogger(__name__)
-		logger.info('Looking for BCM device...')
-		
-		device = usb.core.find(custom_match=cls._dev_matcher)
-		if device is None:
-			raise Exception('Cannot find BCM device - check list of supported devices')
-		logger.info('Found {:04X}:{:04X}'.format(device.idVendor, device.idProduct))
-		
-		logger.debug('Enumerating interfaces...')
-		configuration = device.get_active_configuration()
-		bcm_interface = None
-		for interface in configuration:
-			if interface.bInterfaceClass == 0xff and interface.iInterface == 0x08:
-				if bcm_interface is not None:
-					raise Exception('More than one vendor-specific interface found!')
-				bcm_interface = interface
-		if bcm_interface is None:
-			raise Exception('Cannot find vendor-specific interface')
-		logger.debug('Interface found: {}'.format(bcm_interface._str()))
-		
-		logger.debug('Enumerating endpoints...')
-		bulk_in = None
-		bulk_out = None
-		for endpoint in bcm_interface:
-			if endpoint.bmAttributes & usb.util._ENDPOINT_TRANSFER_TYPE_MASK == usb.util.ENDPOINT_TYPE_BULK:
-				if endpoint.bEndpointAddress & usb.util._ENDPOINT_DIR_MASK == usb.util.ENDPOINT_IN:
-					if bulk_in is not None:
-						raise Exception('More than one BULK IN endpoint found!')
-					bulk_in = endpoint
-					logger.debug('BULK IN found: {}'.format(bulk_in._str()))
-				if endpoint.bEndpointAddress & usb.util._ENDPOINT_DIR_MASK == usb.util.ENDPOINT_OUT:
-					if bulk_out is not None:
-						raise Exception('More than one BULK OUT endpoint found!')
-					bulk_out = endpoint
-					logger.debug('BULK OUT found: {}'.format(bulk_out._str()))
-		
-		if bulk_in is None:
-			raise Exception('BULK IN endpoint not found!')
-		if bulk_out is None:
-			raise Exception('BULK OUT endpoint not found!')
-		
-		logger.debug('Returning {} object...'.format(cls.__name__))
-		return cls(device, bulk_in, bulk_out)
-
-turn_on_seq1 = [
-	"10 2f 04 00",
-	"10 2f 1d 03 05 90 65",
-	"10 2f 2d 00",
-	"10 2f 11 01 f7",
-	"01 27 fc 0c 08 00 01 00 01 00 00 00 00 00 00 00",
-]
-turn_on_seq2 = [
-	"10 20 00 01 01",
-	"10 20 01 02 01 00",
-	"10 20 02 67 01 b9 64 01 00 ff ff 50 00 8b 13 00 10 00 06 00 00 00 00 00 ff 00 00 00 ff 00 00 04 00 00 00 00 03 00 00 00 03 00 0c 00 00 0d 00 00 00 00 00 00 00 00 00 00 33 23 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 00 02 53 3b 0f 00 00 00 00 00 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00",
-	"10 20 02 90 0a ca 05 00 00 00 00 2c 80 01 01 b0 05 01 03 03 03 08 b5 03 01 03 ff c9 0d 24 00 00 00 01 00 bb 00 e4 00 0a 01 02 d6 0d 01 02 00 00 00 00 00 01 00 01 5a 00 8a b2 02 e8 03 c8 1e 06 1f 00 0a 00 30 00 04 24 00 1c 00 75 00 77 00 76 00 1c 00 03 00 0a 00 56 01 00 00 40 04 d7 01 07 dd 32 00 00 00 29 16 08 08 06 04 00 00 00 1f 27 0a 6d 20 00 52 20 00 00 00 01 85 00 00 32 1f 00 00 02 0a 16 00 02 55 55 55 55 55 55 55 55 55 55 55 55 55 1e",
-	"10 20 02 06 01 b7 03 02 00 01",
-	"10 2f 06 01 01",
-	"10 20 02 0e 02 51 08 20 79 ff ff ff ff ff ff 58 01 07",
-	"10 21 00 07 02 04 03 02 05 03 03",
-	"10 20 02 17 01 29 14 46 66 6d 01 01 11 02 02 07 ff 03 02 00 13 04 01 64 07 01 03",
-	"10 20 02 1a 02 61 14 46 66 6d 01 01 11 02 02 07 ff 03 02 00 13 04 01 64 07 01 03 60 01 07",
-	"10 20 02 10 05 30 01 04 31 01 00 32 01 40 38 01 00 50 01 02",
-	"10 20 02 05 01 00 02 fa 00",
-	"10 20 02 0b 01 c2 08 01 08 00 04 80 c3 c9 01",
-	"10 21 03 0d 06 00 01 01 01 02 01 80 01 82 01 06 01",
-]
-
-def turn_on(communicator):
-	communicator.ctrl_transfer(0x41, 0, 1, 3)
-	communicator.talk(turn_on_seq1)
-	communicator.ctrl_transfer(0x41, 1, 0, 3)
-	communicator.talk(turn_on_seq2)
-	communicator.ctrl_transfer(0x41, 1, 1, 3)
-
-def turn_off(communicator):
-	communicator.ctrl_transfer(0x41, 1, 0, 3)
-	communicator.ctrl_transfer(0x41, 0, 0, 3)
-
-
-
-if __name__ == "__main__":
-	if len(sys.argv) < 2:
-		print('Usage: {} [on|off]'.format(sys.argv[0]))
-		sys.exit(2)
-	
-	communicator = BcmCommunicator.find()
-	if sys.argv[1] == 'on':
-		logger.info('Turning NFC on...')
-		turn_on(communicator)
-		logger.info('NFC should be turned on now!')
-	elif sys.argv[1] == 'off':
-		logger.info('Turning NFC off...')
-		turn_off(communicator)
-		logger.info('NFC should be turned off now!')
-	else:
-		raise Exception('Unknown option: {}'.format(sys.argv[1]))
diff --git a/cv2.py b/cv2.py
new file mode 100644
index 0000000..975c7d3
--- /dev/null
+++ b/cv2.py
@@ -0,0 +1,46 @@
+import cvcomm
+
+class ControlVault2:
+	NAME = 'Broadcom ControlVault 2'
+
+	turn_on_seq1 = [
+		"10 2f 04 00",
+		"10 2f 1d 03 05 90 65",
+		"10 2f 2d 00",
+		"10 2f 11 01 f7",
+		"01 27 fc 0c 08 00 01 00 01 00 00 00 00 00 00 00",
+	]
+	turn_on_seq2 = [
+		"10 20 00 01 01",
+		"10 20 01 02 01 00",
+		"10 20 02 67 01 b9 64 01 00 ff ff 50 00 8b 13 00 10 00 06 00 00 00 00 00 ff 00 00 00 ff 00 00 04 00 00 00 00 03 00 00 00 03 00 0c 00 00 0d 00 00 00 00 00 00 00 00 00 00 33 23 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 00 02 53 3b 0f 00 00 00 00 00 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00",
+		"10 20 02 90 0a ca 05 00 00 00 00 2c 80 01 01 b0 05 01 03 03 03 08 b5 03 01 03 ff c9 0d 24 00 00 00 01 00 bb 00 e4 00 0a 01 02 d6 0d 01 02 00 00 00 00 00 01 00 01 5a 00 8a b2 02 e8 03 c8 1e 06 1f 00 0a 00 30 00 04 24 00 1c 00 75 00 77 00 76 00 1c 00 03 00 0a 00 56 01 00 00 40 04 d7 01 07 dd 32 00 00 00 29 16 08 08 06 04 00 00 00 1f 27 0a 6d 20 00 52 20 00 00 00 01 85 00 00 32 1f 00 00 02 0a 16 00 02 55 55 55 55 55 55 55 55 55 55 55 55 55 1e",
+		"10 20 02 06 01 b7 03 02 00 01",
+		"10 2f 06 01 01",
+		"10 20 02 0e 02 51 08 20 79 ff ff ff ff ff ff 58 01 07",
+		"10 21 00 07 02 04 03 02 05 03 03",
+		"10 20 02 17 01 29 14 46 66 6d 01 01 11 02 02 07 ff 03 02 00 13 04 01 64 07 01 03",
+		"10 20 02 1a 02 61 14 46 66 6d 01 01 11 02 02 07 ff 03 02 00 13 04 01 64 07 01 03 60 01 07",
+		"10 20 02 10 05 30 01 04 31 01 00 32 01 40 38 01 00 50 01 02",
+		"10 20 02 05 01 00 02 fa 00",
+		"10 20 02 0b 01 c2 08 01 08 00 04 80 c3 c9 01",
+		"10 21 03 0d 06 00 01 01 01 02 01 80 01 82 01 06 01",
+	]
+
+	def __init__(self, device):
+		self.device = device
+		self.communicator = cvcomm.ControlVaultCommunicator(device)
+
+	def turn_on(self):
+		self.communicator.ctrl_transfer(0x41, 0, 1, 3)
+		self.communicator.talk(self.turn_on_seq1)
+		self.communicator.ctrl_transfer(0x41, 1, 0, 3)
+		self.communicator.talk(self.turn_on_seq2)
+		self.communicator.ctrl_transfer(0x41, 1, 1, 3)
+
+	def turn_off(self):
+		self.communicator.ctrl_transfer(0x41, 1, 0, 3)
+		self.communicator.ctrl_transfer(0x41, 0, 0, 3)
+
+	def reset(self):
+		self.device.reset()
diff --git a/cv3.py b/cv3.py
new file mode 100644
index 0000000..e010616
--- /dev/null
+++ b/cv3.py
@@ -0,0 +1,43 @@
+import cvcomm
+
+class ControlVault3:
+	NAME = 'Broadcom ControlVault 3'
+
+	turn_on_seq1 = [
+		"20 00 01 00",
+	]
+	turn_on_seq2 = [
+		"20 00 01 00",
+		"20 01 00",
+		"20 03 02 01 52",
+		"20 02 1f 0a 21 01 00 28 01 00 30 01 04 31 01 03 54 01 06 5b 01 00 60 01 07 80 01 01 81 01 01 82 01 0e",
+		"21 00 10 05 04 03 02 05 03 03 01 01 01 02 01 01 03 01 01",
+		"21 01 07 00 01 01 03 00 01 05",
+		"20 02 09 01 b9 06 01 00 00 0b 00 00",
+		"20 02 c7 11 18 01 02 2a 01 32 80 01 00 c2 02 03 02 c4 02 00 13 ca 05 00 0f 0d 03 08 cb 01 00 d6 0b 01 01 00 01 12 00 01 00 01 00 01 d8 01 01 de 04 01 00 00 00 e0 07 00 60 93 1c 63 3e 0a e1 02 79 07 e2 2a 48 07 0c 10 00 31 39 39 39 39 39 39 39 39 39 39 41 39 90 90 90 90 3f 90 90 90 88 8a 8c 94 94 28 04 07 00 00 00 00 00 00 00 00 e3 08 17 04 16 0d 10 0c 2b 0b e4 01 37 e5 1e e0 1e 02 12 00 0a 00 10 04 54 54 54 54 2b 52 50 53 4e 20 2d 18 0c 02 07 00 94 70 94 70 20 e6 2d 01 68 00 77 00 8b 00 a7 00 d6 00 22 01 c0 01 9e 58 4c 40 33 26 20 1e 68 00 77 00 8b 00 a7 00 d6 00 22 01 c0 01 5e 58 4c 40 33 2b 26 1e",
+		"2f 1b 06 08 00 00 01 00 00",
+		"20 02 2d 04 29 11 46 66 6d 01 01 11 02 02 03 80 03 02 00 01 04 01 64 2a 01 30 61 11 46 66 6d 01 01 11 02 02 03 80 03 02 00 01 04 01 64 62 01 30",
+		"20 02 0e 04 18 01 01 32 01 40 50 01 02 00 02 2c 01",
+		"21 03 0d 06 00 01 01 01 02 01 06 01 80 01 82 01",
+	]
+
+	def __init__(self, device):
+		self.device = device
+		self.communicator = cvcomm.ControlVaultCommunicator(device)
+
+	def turn_on(self):
+		self.communicator.ctrl_transfer(0x41, 1, 0, 3)
+		self.communicator.talk(self.turn_on_seq1)
+		self.communicator.ctrl_transfer(0x41, 1, 1, 3)
+		self.communicator.ctrl_transfer(0x41, 0, 0, 3)
+		self.communicator.ctrl_transfer(0x41, 0, 1, 3)
+		self.communicator.ctrl_transfer(0x41, 1, 0, 3)
+		self.communicator.talk(self.turn_on_seq2)
+		self.communicator.ctrl_transfer(0x41, 1, 1, 3)
+
+	def turn_off(self):
+		self.communicator.ctrl_transfer(0x41, 1, 0, 3)
+		self.communicator.ctrl_transfer(0x41, 0, 0, 3)
+
+	def reset(self):
+		self.device.reset()
diff --git a/cvcomm.py b/cvcomm.py
new file mode 100644
index 0000000..c2e9823
--- /dev/null
+++ b/cvcomm.py
@@ -0,0 +1,93 @@
+import logging
+import math
+import struct
+import usb.util
+
+def to_hex(val):
+	return ' '.join([bytes([i]).hex() for i in val])
+
+class ControlVaultCommunicator:
+	def __init__(self, device, spi_master=0x01, spi_slave=0x00, spi_crc=0x00):
+		self.logger = logging.getLogger(__name__)
+		self.device = device
+		self.bulk_in, self.bulk_out = self._find_endpoints()
+
+		self.spi_master = spi_master
+		self.spi_slave = spi_slave
+		self.spi_crc = spi_crc
+		self.spi_slave_prefix = struct.pack('>BB', self.spi_slave, self.spi_crc)
+
+	def ctrl_transfer(self, *args, **kwargs):
+		self.logger.debug('Control: {} {}'.format(args, kwargs))
+		return self.device.ctrl_transfer(*args, **kwargs)
+
+	def write(self, *args, **kwargs):
+		return self.bulk_out.write(*args, **kwargs)
+
+	def read(self, *args, **kwargs):
+		return self.bulk_in.read(*args, **kwargs)
+
+	def send_packet(self, payload):
+		length = len(payload)
+		packet = struct.pack('>BBH', self.spi_master, self.spi_crc, length) + payload
+		self.logger.debug('Put: {}'.format(to_hex(packet)))
+		self.write(packet)
+
+	def recv_packet(self):
+		packet = self.read(64, timeout=5000).tobytes()
+		tag = packet[0:2]
+		if tag != self.spi_slave_prefix:
+			raise Exception('Unknown tag: {}'.format(tag.hex()))
+		length = packet[2:4]
+		length = struct.unpack('>H', length)[0]
+
+		for i in range(0, math.ceil(length/64) - 1):
+			packet += self.read(64).tobytes()
+
+		self.logger.debug('Got: {}'.format(to_hex(packet)))
+		return packet[4:]
+
+	def talk(self, exchange):
+		for packet in exchange:
+			self.send_packet(bytes.fromhex(packet))
+			data = self.recv_packet()
+
+			if data[1] == 0x61:
+				packet = self.recv_packet()
+
+	def _find_endpoints(self):
+		self.logger.debug('Enumerating interfaces...')
+		configuration = self.device.get_active_configuration()
+		bcm_interface = None
+		for interface in configuration:
+			if interface.bInterfaceClass == 0xff:
+				if bcm_interface is not None:
+					raise Exception('More than one vendor-specific interface found!')
+				bcm_interface = interface
+		if bcm_interface is None:
+			raise Exception('Cannot find vendor-specific interface')
+		self.logger.debug('Interface found: {}'.format(bcm_interface._str()))
+
+		self.logger.debug('Enumerating endpoints...')
+		bulk_in = None
+		bulk_out = None
+		for endpoint in bcm_interface:
+			if endpoint.bmAttributes & usb.util._ENDPOINT_TRANSFER_TYPE_MASK == usb.util.ENDPOINT_TYPE_BULK:
+				if endpoint.bEndpointAddress & usb.util._ENDPOINT_DIR_MASK == usb.util.ENDPOINT_IN:
+					if bulk_in is not None:
+						raise Exception('More than one BULK IN endpoint found!')
+					bulk_in = endpoint
+					self.logger.debug('BULK IN found: {}'.format(bulk_in._str()))
+				if endpoint.bEndpointAddress & usb.util._ENDPOINT_DIR_MASK == usb.util.ENDPOINT_OUT:
+					if bulk_out is not None:
+						raise Exception('More than one BULK OUT endpoint found!')
+					bulk_out = endpoint
+					self.logger.debug('BULK OUT found: {}'.format(bulk_out._str()))
+
+		if bulk_in is None:
+			raise Exception('BULK IN endpoint not found!')
+		if bulk_out is None:
+			raise Exception('BULK OUT endpoint not found!')
+
+		self.logger.debug('Endpoint discovery successful.')
+		return bulk_in, bulk_out
diff --git a/nfc.py b/nfc.py
new file mode 100755
index 0000000..444e891
--- /dev/null
+++ b/nfc.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python3
+
+import logging
+import sys
+import usb.core
+
+class UsbDeviceMatcher:
+	def __init__(self, properties, handler):
+		self.properties = properties
+		self.handler = handler
+
+	def matches(self, candidate):
+		for prop, value in self.properties.items():
+			if prop not in candidate.__dict__ or candidate.__dict__[prop] != value:
+				return False
+		return True
+
+class UsbDeviceFinder:
+	SUPPORTED_DEVICES = [
+		UsbDeviceMatcher({'idVendor': 0x0A5C, 'idProduct': 0x5832}, lambda device: __import__('cv2').ControlVault2(device)),
+		UsbDeviceMatcher({'idVendor': 0x0A5C, 'idProduct': 0x5834}, lambda device: __import__('cv2').ControlVault2(device)),
+		UsbDeviceMatcher({'idVendor': 0x0A5C, 'idProduct': 0x5843}, lambda device: __import__('cv3').ControlVault3(device)),
+	]
+
+	@classmethod
+	def _dev_matcher(cls, device):
+		for matcher in cls.SUPPORTED_DEVICES:
+			if matcher.matches(device):
+				return True
+		return False
+
+	@classmethod
+	def _cls_matcher(cls, device):
+		for matcher in cls.SUPPORTED_DEVICES:
+			if matcher.matches(device):
+				return matcher.handler(device)
+		raise Exception('Cannot find handler for device {:04X}:{:04X}'.format(dev.idVendor, dev.idProduct))
+
+	@classmethod
+	def find(cls):
+		logger = logging.getLogger(__name__)
+		logger.info('Looking for supported device...')
+
+		device = usb.core.find(custom_match=cls._dev_matcher)
+		if device is None:
+			raise Exception('Cannot find BCM device - check list of supported devices')
+		logger.info('Found {:04X}:{:04X}'.format(device.idVendor, device.idProduct))
+
+		handler = cls._cls_matcher(device)
+		logger.info('Handler {} ({})'.format(handler.__class__.__name__, handler.NAME))
+		return handler
+
+
+if __name__ == "__main__":
+	if len(sys.argv) < 2:
+		print('Usage: {} [on|off|reset]'.format(sys.argv[0]))
+		sys.exit(2)
+
+	logging.basicConfig(level=logging.DEBUG)
+	logger = logging.getLogger(__name__)
+
+	handler = UsbDeviceFinder.find()
+	if sys.argv[1] == 'on':
+		logger.info('Turning NFC on...')
+		handler.turn_on()
+		logger.info('NFC should be turned on now!')
+	elif sys.argv[1] == 'off':
+		logger.info('Turning NFC off...')
+		handler.turn_off()
+		logger.info('NFC should be turned off now!')
+	elif sys.argv[1] == 'reset':
+		logger.info('Resetting device...')
+		handler.reset()
+		logger.info('NFC device has been reset!')
+	else:
+		raise Exception('Unknown option: {}'.format(sys.argv[1]))
diff --git a/traffic.txt b/traffic_cv2.txt
similarity index 100%
rename from traffic.txt
rename to traffic_cv2.txt
diff --git a/traffic_cv3.txt b/traffic_cv3.txt
new file mode 100644
index 0000000..7cc18a0
--- /dev/null
+++ b/traffic_cv3.txt
@@ -0,0 +1,529 @@
+idVendor 0x0A5C
+idProduct 0x5843
+
+Interface 4 - Application-Specific
+EP 1 IN
+EP 1 OUT
+EP 5 IN
+
+Interface 5 - Smart Card
+EP 2 IN
+EP 2 OUT
+EP 6 IN
+
+Interface 6 - Smart Card
+EP 3 IN
+EP 3 OUT
+EP 7 IN
+
+Interface 7 - Vendor Specific
+EP 4 IN
+EP 4 OUT
+EP 8 IN
+
+
+Legend:
+
+>  - New outgoing message (beginning)
+>> - Message continuation (division for readability)
+<  - New incoming message (beginning)
+<< - Message continuation (division for readability)
+# CTRL - Control endpoint communication (bmRequestType, bRequest, wValue, wIndex)
+
+Communication via Endpoint 4
+
+##########
+TURNING ON
+##########
+
+# CTRL 0x41 0x01 0x0000 0x0003
+
+>  01 00 00 04
+   01 = SPI_MASTER
+      00 = NO_CRC
+         00 04 = size
+>> 20 00 01 00
+   20 = NCI_MTS_CMD | NCI_PBF_NO_OR_LAST | NCI_GID_CORE
+      00 = NCI_MSG_CORE_RESET
+         01 = NCI_CORE_PARAM_SIZE_RESET
+            00 = NCI_RESET_TYPE_KEEP_CFG
+
+<  00 00 00 06
+   00 = SPI_SLAVE
+      00 = NO_CRC
+         00 06 = size
+<< 40 00 03 00 10 00
+   40 = NCI_MTS_RSP | NCI_PBF_NO_OR_LAST | NCI_GID_CORE
+      00 = NCI_MSG_CORE_RESET
+         03 = NCI_CORE_PARAM_SIZE_RESET_RSP
+            00 = NCI_STATUS_OK
+               10 = NCI_VERSION (1.0)
+                  00 = NCI_RESET_STATUS_KEPT_CFG
+
+# CTRL 0x41 0x01 0x0001 0x0003
+
+# CTRL 0x41 0x00 0x0000 0x0003
+
+# CTRL 0x41 0x00 0x0001 0x0003
+
+# CTRL 0x41 0x01 0x0000 0x0003
+
+>  01 00 00 04
+   01 = SPI_MASTER
+      00 = NO_CRC
+         00 04 = size
+>> 20 00 01 00
+   20 = NCI_MTS_CMD | NCI_PBF_NO_OR_LAST | NCI_GID_CORE
+      00 = NCI_MSG_CORE_RESET
+         01 = NCI_CORE_PARAM_SIZE_RESET
+            00 = NCI_RESET_TYPE_KEEP_CFG
+
+<  00 00 00 06
+   00 = SPI_SLAVE
+      00 = NO_CRC
+         00 06 = size
+<< 40 00 03 00 11 00
+   40 = NCI_MTS_RSP | NCI_PBF_NO_OR_LAST | NCI_GID_CORE
+      00 = NCI_MSG_CORE_RESET
+         03 = NCI_CORE_PARAM_SIZE_RESET_RSP
+            00 = NCI_STATUS_OK
+               11 = NCI_VERSION (1.1)
+                  00 = NCI_RESET_STATUS_KEPT_CFG
+
+>  01 00 00 03
+   01 = SPI_MASTER
+      00 = NO_CRC
+         00 03 = size
+>> 20 01 00
+   20 = NCI_MTS_CMD | NCI_PBF_NO_OR_LAST | NCI_GID_CORE
+      01 = NCI_MSG_CORE_INIT
+         00 = size
+
+<  00 00 00 1a
+   00 = SPI_SLAVE
+      00 = NO_CRC
+         00 1a = size
+<< 40 01 17 00
+   40 = NCI_MTS_RSP | NCI_PBF_NO_OR_LAST | NCI_GID_CORE
+      01 = NCI_MSG_CORE_INIT
+         17 = size
+            00 = NCI_STATUS_OK
+<< 03 0e 02 01
+   03 0e 02 01 NFCC_FEATURES
+   02 = multiple NFCEEs
+   01 = RF_DISCOVER_CMD supported
+      08 = AID-based routing supported
+      04 = Protocol-based routing supported
+      02 = Technology-based routing supported
+         02 = Switched-off state supported
+            01 = Proprietary parameters
+<< 06 00 01 02 03 81 83
+   06 = num_of_ifaces (6)
+      00 = NCI_INTERFACE_EE_DIRECT_RF
+         01 = NCI_INTERFACE_FRAME
+            02 = NCI_INTERFACE_ISO_DEP
+               03 = NCI_INTERFACE_NFC_DEP
+                  81 = Proprietary Interface (81)
+                     83 = Proprietary Interface (83)
+
+<< 08 00 04 ff 0f 00 2e 04 10 2b 41
+   08 = num_of_conns_max (8)
+      00 04 = num_of_routes_max
+            ff = size_control_payload_max
+               0f 00 = size_large_params_max
+                     2e = manufacturer_id
+                        04 10 2b 41 = manufacturer_specific
+
+>  01 00 00 05
+   01 = SPI_MASTER
+      00 = NO_CRC
+         00 05 = size
+>> 20 03 02 01 52
+   20 = NCI_MTS_CMD | NCI_PBF_NO_OR_LAST | NCI_GID_CORE
+      03 = NCI_MSG_CORE_GET_CONFIG
+         02 = num_of_params (2)
+            01 = CON_DEVICES_LIMIT
+               52 = LF_T3T_MAX
+
+<  00 00 00 08
+   00 = SPI_SLAVE
+      00 = NO_CRC
+         00 08 = size
+<< 40 03 05 00 01 52 01 10
+   40 = NCI_MTS_RSP | NCI_PBF_NO_OR_LAST | NCI_GID_CORE
+      03 = NCI_MSG_CORE_GET_CONFIG
+         ???
+
+>  01 00 00 22
+   01 = SPI_MASTER
+      00 = NO_CRC
+         00 22 = size
+>> 20 02 1f 0a
+   20 = NCI_MTS_CMD | NCI_PBF_NO_OR_LAST | NCI_GID_CORE
+      02 = NCI_MSG_CORE_SET_CONFIG
+         1f = size
+            0a = num_of_params (10)
+>> 21 01 00
+   21 = PI_BIT_RATE
+      01 = size_of_param
+         00 = 106 Kbit/s
+>> 28 01 00
+   28 = PN_NFC_DEP_SPEED
+      01 = size_of_param
+         00 = Highest Available Bitrate
+>> 30 01 04
+   30 = LA_BIT_FRAME_SDD
+      01 = size_of_param
+         04 = Bit Frame SDD value to be sent in Byte 1 of SENS_RES
+>> 31 01 03
+   31 = LA_PLATFORM_CONFIG
+      01 = size_of_param
+         03 = Platform Configuration value to be sent in Byte 2 of SENS_RES
+>> 54 01 06
+   54 = LF_CON_BITR_F
+      01 = size_of_param
+         02 = listen for 212 kbps
+         04 = listen for 424 kbps
+>> 5b 01 00
+   5b = LI_BIT_RATE
+      01 = size_of_param
+         00 = 106 Kbit/s
+>> 60 01 07
+   60 = LN_WT
+      01 = size_of_param
+         07 = waiting time (7)
+>> 80 01 01
+   80 = RF_FIELD_INFO
+      01 = size_of_param
+         01 = RF_FIELD_INFO_NTF allowed
+>> 81 01 01
+   81 = RF_NFCEE_ACTION
+      01 = size_of_param
+         01 = send RF NFCEE Actions
+>> 82 01 0e
+   82 = NFCDEP_OP
+      01 = size_of_param
+         02 = ATTENTION only for error recovery
+         04 = don't send msgs without Transport Data Bytes
+         08 = chaining uses max number of Transport Data Bytes
+
+<  00 00 00 05
+   00 = SPI_SLAVE
+      00 = NO_CRC
+         00 05 = size
+<< 40 02 02 00 00
+   40 = NCI_MTS_RSP | NCI_PBF_NO_OR_LAST | NCI_GID_CORE
+      02 = NCI_MSG_CORE_SET_CONFIG
+         02 = size
+            00 = NCI_STATUS_OK
+               00 = num_of_invalid_params (0)
+
+>  01 00 00 13
+   01 = SPI_MASTER
+      00 = NO_CRC
+         00 13 = size
+>> 21 00 10 05
+   21 = NCI_MTS_CMD | NCI_PBF_NO_OR_LAST | NCI_GID_RF_MANAGE
+      00 = NCI_MSG_RF_DISCOVER_MAP
+         10 = size (16)
+            05 = num_mapping_configs
+>> 04 03 02
+   04 = NCI_PROTOCOL_ISO_DEP
+      03 = NCI_INTERFACE_MODE_POLL_N_LISTEN
+         02 = NCI_INTERFACE_ISO_DEP
+>> 05 03 03
+   05 = NCI_PROTOCOL_NFC_DEP
+      03 = NCI_INTERFACE_MODE_POLL_N_LISTEN
+         03 = NCI_INTERFACE_NFC_DEP
+>> 01 01 01
+   01 = PROTOCOL_T1T
+      01 = NCI_INTERFACE_MODE_POLL
+         01 = NCI_INTERFACE_FRAME
+>> 02 01 01
+   02 = PROTOCOL_T2T
+      01 = NCI_INTERFACE_MODE_POLL
+         01 = NCI_INTERFACE_FRAME
+>> 03 01 01
+   03 = PROTOCOL_T3T
+      01 = NCI_INTERFACE_MODE_POLL
+         01 = NCI_INTERFACE_FRAME
+
+<  00 00 00 04
+   00 = SPI_SLAVE
+      00 = NO_CRC
+         00 04 = size
+<< 61 07 01 00
+   61 = NCI_MTS_NTF | NCI_PBF_NO_OR_LAST | NCI_GID_RF_MANAGE
+      07 = NCI_MSG_RF_FIELD
+         01 = size
+            00 = No RF field
+
+<  00 00 00 04
+   00 = SPI_SLAVE
+      00 = NO_CRC
+         00 04 = size
+<< 41 00 01 00
+   41 = NCI_MTS_RSP | NCI_PBF_NO_OR_LAST | NCI_GID_RF_MANAGE
+      00 = NCI_MSG_RF_DISCOVER_MAP
+         01 = NCI_DISCOVER_PARAM_SIZE_RSP
+            00 = NCI_STATUS_OK
+
+
+>  01 00 00 0a
+   01 = SPI_MASTER
+      00 = NO_CRC
+         00 0a = size
+>> 21 01 07 00 01 01 03 00 01 05
+   21 = NCI_MTS_CMD | NCI_PBF_NO_OR_LAST | NCI_GID_RF_MANAGE
+      01 = NCI_MSG_RF_SET_ROUTING
+         07 = size
+            00 = last message
+               01 = routing_entries (1)
+                  01 = protocol-based routing
+                     03 = length (3)
+                        00 = DH NFCEE ID
+                           01 = power state (1 - switched on)
+                              05 = PROTOCOL_NFC_DEP
+
+
+<  00 00 00 04
+   00 = SPI_SLAVE
+      00 = NO_CRC
+         00 04 = size
+<< 41 01 01 00
+   41 = NCI_MTS_RSP | NCI_PBF_NO_OR_LAST | NCI_GID_RF_MANAGE
+      01 = NCI_MSG_RF_SET_ROUTING
+         01 = size
+            00 = NCI_STATUS_OK
+
+>  01 00 00 0c
+   01 = SPI_MASTER
+      00 = NO_CRC
+         00 0c = size
+>> 20 02 09 01
+   20 = NCI_MTS_CMD | NCI_PBF_NO_OR_LAST | NCI_GID_CORE
+      02 = NCI_MSG_CORE_SET_CONFIG
+         09 = size
+            01 = num_of_params
+>> b9 06 01 00 00 0b 00 00
+   b9 = Proprietary parameter (b9)
+      06 = size_of_param
+
+<  00 00 00 05
+   00 = SPI_SLAVE
+      00 = NO_CRC
+         00 05 = size
+<< 40 02 02 00 00
+   40 = NCI_MTS_RSP | NCI_PBF_NO_OR_LAST | NCI_GID_CORE
+      02 = NCI_MSG_CORE_SET_CONFIG
+         02 = size
+            00 = NCI_STATUS_OK
+               00 = num_of_invalid_params (0)
+
+>  01 00 00 ca
+   01 = SPI_MASTER
+      00 = NO_CRC
+         00 ca = size
+>> 20 02 c7 11
+   20 = NCI_MTS_CMD | NCI_PBF_NO_OR_LAST | NCI_GID_CORE
+      02 = NCI_MSG_CORE_SET_CONFIG
+         c7 = size
+            11 = num_of_params (17)
+>> 18 01 02
+   18 = PF_BIT_RATE
+      01 = size_of_param
+         02 = 424 Kbit/s
+>> 2a 01 32
+   2a = PN_ATR_REQ_CONFIG
+      01 = size_of_param
+         30 = 0011b for LLCP
+         02 = ?
+>> 80 01 00
+   80 = RF_FIELD_INFO
+      01 = size_of_param
+         00 = RF_FIELD_INFO_NTF not allowed
+>> c2 02 03 02
+   c2 = Proprietary parameter (c2)
+      02 = size_of_param
+>> c4 02 00 13
+   c4 = Proprietary parameter (c4)
+      02 = size_of_param
+>> ca 05 00 0f 0d 03 08
+   ca = Proprietary parameter (ca)
+      05 = size_of_param
+>> cb 01 00
+   cb = Proprietary parameter (cb)
+      01 = size_of_param
+>> d6 0b 01 01 00 01 12 00 01 00 01 00 01
+   d6 = Proprietary parameter (d6)
+      0b = size_of_param
+>> d8 01 01
+   d8 = Proprietary parameter (d8)
+      01 = size_of_param
+>> de 04 01 00 00 00
+   de = Proprietary parameter (de)
+      04 = size_of_param
+>> e0 07 00 60 93 1c 63 3e 0a
+   e0 = Proprietary parameter (e0)
+      07 = size_of_param
+>> e1 02 79 07
+   e1 = Proprietary parameter (e1)
+      02 = size_of_param
+>> e2 2a 48 07 0c 10 00 31 39 39 39 39 39 39 39 39 39 39 41 39 90 90 90 90 3f 90 90 90 88 8a 8c 94 94 28 04 07 00 00 00 00 00 00 00 00
+   e2 = Proprietary parameter (e2)
+      2a = size_of_param
+>> e3 08 17 04 16 0d 10 0c 2b 0b
+   e3 = Proprietary parameter (e3)
+      08 = size_of_param
+>> e4 01 37
+   e4 = Proprietary parameter (e4)
+      01 = size_of_param
+>> e5 1e e0 1e 02 12 00 0a 00 10 04 54 54 54 54 2b 52 50 53 4e 20 2d 18 0c 02 07 00 94 70 94 70 20
+   e5 = Proprietary parameter (e5)
+      1e = size_of_param
+>> e6 2d 01 68 00 77 00 8b 00 a7 00 d6 00 22 01 c0 01 9e 58 4c 40 33 26 20 1e 68 00 77 00 8b 00 a7 00 d6 00 22 01 c0 01 5e 58 4c 40 33 2b 26 1e
+   e6 = Proprietary parameter (e6)
+      2d = size_of_param
+
+<  00 00 00 05
+   00 = SPI_SLAVE
+      00 = NO_CRC
+         00 05 = size
+<< 40 02 02 00 00
+   40 = NCI_MTS_RSP | NCI_PBF_NO_OR_LAST | NCI_GID_CORE
+      02 = NCI_MSG_CORE_SET_CONFIG
+         02 = size
+            00 = NCI_STATUS_OK
+               00 = num_of_invalid_params (0)
+
+>  01 00 00 09
+   01 = SPI_MASTER
+      00 = NO_CRC
+         00 09 = size
+>> 2f 1b 06 08 00 00 01 00 00
+   2f = NCI_MTS_CMD | NCI_PBF_NO_OR_LAST | NCI_GID_PROP
+      1b = Proprietary message (1b)
+         06 = size
+
+<  00 00 00 04
+   00 = SPI_SLAVE
+      00 = NO_CRC
+         00 04 = size
+<< 4f 1b 01 00
+   4f = NCI_MTS_RSP | NCI_PBF_NO_OR_LAST | NCI_GID_PROP
+      1b = Proprietary message (1b)
+         01 = size
+
+>  01 00 00 30
+   01 = SPI_MASTER
+      00 = NO_CRC
+         00 30 = size
+>> 20 02 2d 04
+   20 = NCI_MTS_CMD | NCI_PBF_NO_OR_LAST | NCI_GID_CORE
+      02 = NCI_MSG_CORE_SET_CONFIG
+         2d = size
+            04 = num_of_params (4)
+>> 29 11 46 66 6d 01 01 11 02 02 03 80 03 02 00 01 04 01 64
+   29 = PN_ATR_REQ_GEN_BYTES
+      11 = size_of_param
+         General Bytes for ATR_REQ
+>> 2a 01 30
+   2a = PN_ATR_REQ_CONFIG
+      01 = size_of_param
+         30 = 0011b for LLCP
+>> 61 11 46 66 6d 01 01 11 02 02 03 80 03 02 00 01 04 01 64
+   61 = LN_ATR_RES_GEN_BYTES
+      11 = size_of_param
+         General Bytes in ATR_RES
+>> 62 01 30
+   62 = LN_ATR_RES_CONFIG
+      01 = size_of_param
+         30 = 0011b for LLCP
+
+<  00 00 00 05
+   00 = SPI_SLAVE
+      00 = NO_CRC
+         00 05 = size
+<< 40 02 02 00 00
+   40 = NCI_MTS_RSP | NCI_PBF_NO_OR_LAST | NCI_GID_CORE
+      02 = NCI_MSG_CORE_SET_CONFIG
+         02 = size
+            00 = NCI_STATUS_OK
+               00 = num_of_invalid_params (0)
+
+>  01 00 00 11
+   01 = SPI_MASTER
+      00 = NO_CRC
+         00 11 = size
+>> 20 02 0e 04
+   20 = NCI_MTS_CMD | NCI_PBF_NO_OR_LAST | NCI_GID_CORE
+      02 = NCI_MSG_CORE_SET_CONFIG
+         0e = size
+            04 = num_of_params (4)
+>> 18 01 01
+   18 = PF_BIT_RATE
+      01 = size_of_param
+         01 = 212 Kbit/s
+>> 32 01 40
+   32 = LA_SEL_INFO
+      01 = size_of_param
+         40 = NFC-DEP
+>> 50 01 02
+   50 = LF_PROTOCOL_TYPE
+      01 = size_of_param
+         02 = NFC-DEP
+>> 00 02 2c 01
+   00 = TOTAL_DURATION
+      02 = size_of_param
+         2c 01 = 11 256 ms
+
+<  00 00 00 05
+   00 = SPI_SLAVE
+      00 = NO_CRC
+         00 05 = size
+<< 40 02 02 00 00
+   40 = NCI_MTS_RSP | NCI_PBF_NO_OR_LAST | NCI_GID_CORE
+      02 = NCI_MSG_CORE_SET_CONFIG
+         02 = size
+            00 = NCI_STATUS_OK
+               00 = num_of_invalid_params (0)
+
+>  01 00 00 10
+   01 = SPI_MASTER
+      00 = NO_CRC
+         00 10 = size
+>> 21 03 0d 06
+   21 = NCI_MTS_CMD | NCI_PBF_NO_OR_LAST | NCI_GID_RF_MANAGE
+      03 = NCI_MSG_RF_DISCOVER
+         0d = size
+            06 = num_of_configs (6)
+>> 00 01
+   00 = NFC_A_PASSIVE_POLL_MODE
+      01 = every discovery period
+>> 01 01
+   01 = NFC_B_PASSIVE_POLL_MODE
+      01 = every discovery period
+>> 02 01
+   02 = NFC_F_PASSIVE_POLL_MODE
+      01 = every discovery period
+>> 06 01
+   06 = NFC_15693_PASSIVE_POLL_MODE
+      01 = every discovery period
+>> 80 01
+   80 = NFC_A_PASSIVE_LISTEN_MODE
+      01 = every discovery period
+>> 82 01
+   82 = NFC_F_PASSIVE_LISTEN_MODE
+      01 = every discovery period
+
+<  00 00 00 04
+   00 = SPI_SLAVE
+      00 = NO_CRC
+         00 04 = size
+<< 41 03 01 00
+   41 = NCI_MTS_RSP | NCI_PBF_NO_OR_LAST | NCI_GID_RF_MANAGE
+      03 = NCI_MSG_RF_DISCOVER
+         01 = size
+            00 = STATUS_OK
+
+# CTRL 0x41 0x01 0x0001 0x0003

--
Gitblit v1.9.1