diff --git a/experimental/bluepy/commBLE.py b/experimental/bluepy/commBLE.py new file mode 100644 index 0000000..150ea58 --- /dev/null +++ b/experimental/bluepy/commBLE.py @@ -0,0 +1,92 @@ +""" +******************************************************************************* +* Ledger Blue +* (c) 2016 Ledger +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +******************************************************************************** +""" + +from abc import ABCMeta, abstractmethod +from ledgerblue.comm import Dongle +from ledgerblue.commException import CommException +from ledgerblue.ledgerWrapper import wrapCommandAPDU, unwrapResponseAPDU +from binascii import hexlify +from bluepy import btle + +SERVICE_UUID = "D973F2E0-B19E-11E2-9E96-0800200C9A66" +WRITE_CHARACTERISTICS_UUID = "D973F2E2-B19E-11E2-9E96-0800200C9A66" +NOTIFY_CHARACTERISTICS_UUID = "D973F2E1-B19E-11E2-9E96-0800200C9A66" +CLIENT_CHARACTERISTICS_CONFIG_DESCRIPTOR_UUID = "00002902-0000-1000-8000-00805f9b34fb" +ENABLE_NOTIFICATION = "0100".decode('hex') +DEFAULT_BLE_CHUNK = 20 +BLE_NOTIFICATION_TIMEOUT = 5.0 + +class BLEDongleDelegate(btle.DefaultDelegate): + def __init__(self, dongle): + btle.DefaultDelegate.__init__(self) + self.dongle = dongle + + def handleNotification(self, cHandle, data): + self.dongle.result += bytearray(data) + +class BLEDongle(Dongle): + + # Must be called with the Client Characteristics Configuration descriptor handle as bluepy fails to retrieve it + # From gatttools + # [device mac][LE]> char-desc + # handle: 0x000f, uuid: 00002902-0000-1000-8000-00805f9b34fb + def __init__(self, bleAddress, configDescriptor, debug=False): + self.device = btle.Peripheral(bleAddress) + self.device.setDelegate(BLEDongleDelegate(self)) + self.service = self.device.getServiceByUUID(SERVICE_UUID) + self.writeCharacteristic = self.service.getCharacteristics(forUUID=WRITE_CHARACTERISTICS_UUID)[0] + self.device.writeCharacteristic(configDescriptor, ENABLE_NOTIFICATION, withResponse=True) + self.debug = debug + self.opened = True + + def exchange(self, apdu, timeout=20000): + if self.debug: + print "=> %s" % hexlify(apdu) + apdu = wrapCommandAPDU(0, apdu, DEFAULT_BLE_CHUNK, True) + offset = 0 + while(offset < len(apdu)): + data = apdu[offset:offset + DEFAULT_BLE_CHUNK] + self.writeCharacteristic.write(data, withResponse=True) + offset += DEFAULT_BLE_CHUNK + self.result = "" + while True: + if not self.device.waitForNotifications(BLE_NOTIFICATION_TIMEOUT): + raise CommException("Timeout") + response = unwrapResponseAPDU(0, self.result, DEFAULT_BLE_CHUNK, True) + if response is not None: + result = response + dataStart = 0 + swOffset = len(response) - 2 + dataLength = len(response) - 2 + break + sw = (result[swOffset] << 8) + result[swOffset + 1] + response = result[dataStart : dataLength + dataStart] + if self.debug: + print "<= %s%.2x" % (hexlify(response), sw) + if sw <> 0x9000: + raise CommException("Invalid status %04x" % sw, sw) + return response + + def close(self): + if self.opened: + try: + self.device.disconnect() + except: + pass + self.opened = False diff --git a/ledgerblue/ledgerWrapper.py b/ledgerblue/ledgerWrapper.py index 89a50a2..bb0c5e3 100644 --- a/ledgerblue/ledgerWrapper.py +++ b/ledgerblue/ledgerWrapper.py @@ -20,40 +20,54 @@ import struct from .commException import CommException -def wrapCommandAPDU(channel, command, packetSize): +def wrapCommandAPDU(channel, command, packetSize, ble=False): if packetSize < 3: raise CommException("Can't handle Ledger framing with less than 3 bytes for the report") sequenceIdx = 0 - offset = 0 - result = struct.pack(">HBHH", channel, 0x05, sequenceIdx, len(command)) + offset = 0 + if not ble: + result = struct.pack(">H", channel) + extraHeaderSize = 2 + else: + result = "" + extraHeaderSize = 0 + result += struct.pack(">BHH", 0x05, sequenceIdx, len(command)) sequenceIdx = sequenceIdx + 1 - if len(command) > packetSize - 7: - blockSize = packetSize - 7 + if len(command) > packetSize - 5 - extraHeaderSize: + blockSize = packetSize - 5 - extraHeaderSize else: blockSize = len(command) result += command[offset : offset + blockSize] offset = offset + blockSize while offset <> len(command): - result += struct.pack(">HBH", channel, 0x05, sequenceIdx) + if not ble: + result += struct.pack(">H", channel) + result += struct.pack(">BH", 0x05, sequenceIdx) sequenceIdx = sequenceIdx + 1 - if (len(command) - offset) > packetSize - 5: - blockSize = packetSize - 5 + if (len(command) - offset) > packetSize - 3 - extraHeaderSize: + blockSize = packetSize - 3 - extraHeaderSize else: blockSize = len(command) - offset result += command[offset : offset + blockSize] offset = offset + blockSize - while (len(result) % packetSize) <> 0: - result += "\x00" + if not ble: + while (len(result) % packetSize) <> 0: + result += "\x00" return bytearray(result) -def unwrapResponseAPDU(channel, data, packetSize): +def unwrapResponseAPDU(channel, data, packetSize, ble=False): sequenceIdx = 0 offset = 0 - if ((data is None) or (len(data) < 7 + 5)): + if not ble: + extraHeaderSize = 2 + else: + extraHeaderSize = 0 + if ((data is None) or (len(data) < 5 + extraHeaderSize + 5)): return None - if struct.unpack(">H", str(data[offset : offset + 2]))[0] <> channel: - raise CommException("Invalid channel") - offset += 2 + if not ble: + if struct.unpack(">H", str(data[offset : offset + 2]))[0] <> channel: + raise CommException("Invalid channel") + offset += 2 if data[offset] <> 0x05: raise CommException("Invalid tag") offset += 1 @@ -62,10 +76,10 @@ def unwrapResponseAPDU(channel, data, packetSize): offset += 2 responseLength = struct.unpack(">H", str(data[offset : offset + 2]))[0] offset += 2 - if len(data) < 7 + responseLength: + if len(data) < 5 + extraHeaderSize + responseLength: return None - if responseLength > packetSize - 7: - blockSize = packetSize - 7 + if responseLength > packetSize - 5 - extraHeaderSize: + blockSize = packetSize - 5 - extraHeaderSize else: blockSize = responseLength result = data[offset : offset + blockSize] @@ -74,17 +88,18 @@ def unwrapResponseAPDU(channel, data, packetSize): sequenceIdx = sequenceIdx + 1 if (offset == len(data)): return None - if struct.unpack(">H", str(data[offset : offset + 2]))[0] <> channel: - raise CommException("Invalid channel") - offset += 2 + if not ble: + if struct.unpack(">H", str(data[offset : offset + 2]))[0] <> channel: + raise CommException("Invalid channel") + offset += 2 if data[offset] <> 0x05: raise CommException("Invalid tag") offset += 1 if struct.unpack(">H", str(data[offset : offset + 2]))[0] <> sequenceIdx: raise CommException("Invalid sequence") offset += 2 - if (responseLength - len(result)) > packetSize - 5: - blockSize = packetSize - 5 + if (responseLength - len(result)) > packetSize - 3 - extraHeaderSize: + blockSize = packetSize - 3 - extraHeaderSize else: blockSize = responseLength - len(result) result += data[offset : offset + blockSize]