Python Mesh

Pykonik 2019

czyli zdalnie sterowane żarówki

Agenda

  • Słówko o topologii sieci
  • Stos BT Mesh
  • Adresowanie i subskrypcje
  • Połączenie z BT Mesh
  • Demo!

Gwiazda

  • Ethernet (10BaseT)
  • IP

rozszerzona

Magistrala

  • Ethernet (10Base2)
  • CAN, I2C, RS-485

Siatka

Pobożne życzenia

Smutna rzeczywistość

Siatka

Retransmisje

Relay

Bezpieczeństwo

Idempotentność

listonosz nie

czyta poczty

powtórzenie komendy

nie ma skutków ubocznych

BT Mesh

Stos

Network

Transport

Application

Bearer

IP

TCP

HTTP

Ethernet

BT Mesh

Rozgłoszenia

Połączenia

Bearer

BT Mesh

Preamble Access Address Header MAC Address Payload CRC
1 byte 4 bytes 2 bytes 6 bytes 0-31 bytes 3 bytes

Bearer

Length Type Data ... Length Type Data
1 byte 1 byte 0-29 bytes ... 1 byte 1 byte 0-29 bytes

Losowy adres

0x2a

BT Mesh

Network

IVI NID CTL TTL SEQ SRC DST PDU MIC
1 bit 7 bits 1 bit 7 bits 24 bits 16 bits 16 bits 8-128 bits 32-64 bits

Adresy

Podpis

Numer pakietu

Ramka

transportowa

Ukryte

Tajne

Jawne

Jawne

Identyfikator klucza

BT Mesh

Szyfrowanie

Plaintext

Nonce

Key

AES

Ciphertext
MIC

IV Index SEQ SRC ...
32bit 24bit 16 bit

Ogólnie znane

Część pakietu

Numer pakietu jest niepowtarzalny!

DST PDU

BT Mesh

Network

from bitstring import pack
from crypto import aes_ccm, aes_ecb


network_nonce = pack('uint:8, uint:1, uint:7, '
                     'uintbe:24, uintbe:16, pad:16, uintbe:32',
                     0x00, ctl, ttl, seq, src, iv_index)

encrypted_pdu = aes_ccm(encryption_key, nonce,
                        bitstring.pack('uintbe:16, bytes', dst, transport_pdu))

network_header = pack('uint:1, uint:7, uintbe:24, uintbe:16',
                      ctl, ttl, seq, src)

# nie całkiem prawda ;-)
privacy_nonce = pack('pad:40, uintbe:32, bytes:7',
                     iv_index, encrypted_pdu[:7])

obfuscated_header = aes_ecb(privacy_key, privacy_nonce,
                            network_header) 

network_pdu = pack('uint:1, uint:7, bits, bytes',
                   iv_index & 1, nid, obfuscated_header, network_pdu)

BT Mesh

Transport

SEG AKF AID PDU MIC
1 bit 1 bit 6 bits 0-88 bits 32-64 bits

Identyfikator klucza

Fragmentacja

Podpis

Ramka aplikacyjna

Tajne

Jawne

Jawne

BT Mesh

Transport

from bitstring import pack
from crypto import aes_ccm


transport_nonce = pack('uint:8, uint:1, pad:7, '
                       'uintbe:24, uintbe:16, pad:16, uintbe:32',
                       0x01, long_mic, seq, src, dst, iv_index)

encrypted_pdu = aes_ccm(application_key, application_nonce,
                        application_pdu)

seg = len(encrypted_pdu) > 15

if not seg:
    transport_pdu = pack('uint:1, uint:1, uint:6, bytes',
                         seg, akf, aid, encrypted_pdu)
else:
    raise NotImplementedError  # ;-)

BT Mesh

Application

OPCODE PARAMETERS
1-3 bytes 0-379 bytes

'Assigned

Numbers'

  • 0x00: Config Application Key Add
  • 0x80 0x00: Config Application Key Delete
  • 0x80 0x01: Config Application Key Get
  • 0x80 0x31: Health Fault Get
  • 0x05: Health Fault Status
  • ...

BT Mesh

Application

application_pdu = pack('bits, bits',
                       opcode, parameters)

opcode = '\x80\x05'  # "health: attention set"
parameters = pack('uint:8', timeout)

opcode = '\x82\x03'  # "generic on-off: set unacknowledged"
parameters = pack('uint:8, uint:8, uint:2, uint:6, uint:8',
                  trigger, transaction, resolution, steps, delay)

opcode = '\x80\x1b'  # "config: subscription add"
parameters = ...

BT Mesh

Adresowanie

0 15 bits
1 0 14 bits
1 1 14 bits
0 0

Unassigned

Unicast

Virtual

Group

BT Mesh

Elementy i modele

Adres Element Modele
0x0010 #0 Health
Config
Scene Server
Presence Sensor
0x0011 #1 Lightness Controller
Ambient Light Sensor
0x0012 #2 Scene Translator
#0 #1 #2 #3
OnOff OnOff OnOff OnOff
0x0010 0x0011 0x0012 0x0013

Element

Adres unicast

Model

BT Mesh

Subskrypcje

0x8000
0x8001

0x0010

0x0020

0x0030

0x0040

0x8000

0x8001

0x8000

0x0050

0x8001

0x8002

BT Mesh

Subskrypcje + relay

0x8000
0x8001

0x0010

0x0030

0x8001

0x0050

0x8001

relay

BT Mesh

Retransmisje

włącz się
za 90ms

włącz się
za 30ms

włącz się
za 60ms

czas

Wszystko pięknie, ale jak się do tego podłączyć?

Mesh Proxy

Ogólnie

Proxy

GATT

0x0050

PC

Mesh Proxy

GATT

 

BlueZ

 

DBus

 

Python

 

Linux

BlueZ

Mesh Proxy

Usługi

Profil

Usługa

Charakterystyka

Charakterystyka

Usługa

Charakterystyka

Charakterystyka

BLUETOOTH_SIG = \
    '0000-1000-8000-00805f9b34fb'


proxy_service = \
    '00001828-' + BLUETOOTH_SIG


write_char = \
    '00002add-' + BLUETOOTH_SIG


read_char = \
    '00002ade-' + BLUETOOTH_SIG

Mesh Proxy

Rozgłoszenia

Preamble Access Address Header MAC Address Payload CRC
1 byte 4 bytes 2 bytes 6 bytes 0-31 bytes 3 bytes
Length Type Data ... Length Type Data
1 byte 1 byte 0-29 bytes ... 1 byte 1 byte 0-29 bytes

Prawdziwy adres

0x03

0x16

Mesh Proxy

Rozgłoszenia

from pydbus import SystemBus

bus = SystemBus()

adapter = bus.get('org.bluez',
                  '/org/bluez/hci0')

adapter.StartDiscovery()

...

def device_discovered(device):
    network_id = device.ServiceData.get(ServiceId.MESH_PROXY)
    proxy_mac = device.Address

Mesh Proxy

from pydbus import SystemBus

bus = SystemBus()

adapter = bus.get('org.bluez',
                  '/org/bluez/hci0')

adapter.StartDiscovery()

device = bus.get('org.bluez',
                 '/org/bluez/hci0/dev_%s' % proxy_mac)

device.Connect()

service = bus.get('org.bluez',
                  '/org/bluez/hci0/dev_%s/service_%s' % (proxy_mac,
                                                         proxy_service))

send = bus.get('org.bluez',
               '/org/bluez/hci0/dev_%s/char_%s' % (proxy_mac,
                                                   write_char))
recv = bus.get('org.bluez',
               '/org/bluez/hci0/dev_%s/char_%s' % (proxy_mac,
                                                   read_char))

Usługi

Mesh Proxy

send = bus.get('org.bluez',
               '/org/bluez/hci0/dev_%s/char_%s' % (proxy_mac,
                                                   write_char))
recv = bus.get('org.bluez',
               '/org/bluez/hci0/dev_%s/char_%s' % (proxy_mac,
                                                   read_char))


def packet_send(packet):
    send.WriteValue(bytes(....))


def properties_changed(properties):
    value = properties.get('Value')
    if value is not None:
        packet_receive(bytes(value))


recv.PropertiesChanged.connect(packet_received)
recv.StartNotify()

GATT Bearer

Mesh Proxy

Szczegółowo

GATT

BlueZ

DBus

Python

Linux

Relay

Proxy

pip install bluetooth-mesh

SilvairGit/python-bluetooth-mesh

GPL 2.0

Dziękuję za uwagę!

Pytania?

Bluetooth Mesh

By Michał Lowas-Rzechonek

Bluetooth Mesh

Remote controlled bulbs

  • 582