Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions doc/scapy/layers/bluetooth.rst
Original file line number Diff line number Diff line change
Expand Up @@ -384,8 +384,46 @@ Setting up advertising
Changing advertisements may not take effect until advertisements have first
been :ref:`stopped <le-adv-stop>`.

Eddystone URL beacon
^^^^^^^^^^^^^^^^^^^^
AltBeacon
^^^^^^^^^

`AltBeacon`__ is a proximity beacon protocol developed by Radius Networks. This
example sets up a virtual AltBeacon:

__ https://github.com/AltBeacon/spec

.. code-block:: python3

# Load the contrib module for AltBeacon
load_contrib('altbeacon')

ab = AltBeacon(
id1='2f234454-cf6d-4a0f-adf2-f4911ba9ffa6',
id2=1,
id3=2,
tx_power=-59,
)

bt.sr(ab.build_set_advertising_data())

Once :ref:`advertising has been started <le-adv-start>`, the beacon may then be
detected with `Beacon Locator`__ (Android).

.. note::

Beacon Locator v1.2.2 `incorrectly reports the beacon as being an
iBeacon`__, but the values are otherwise correct.

__ https://github.com/vitas/beaconloc
__ https://github.com/vitas/beaconloc/issues/32

Eddystone
^^^^^^^^^

`Eddystone`__ is a proximity beacon protocol developed by Google. This uses an
Eddystone-specific service data field.

__ https://github.com/google/eddystone/

This example sets up a virtual `Eddystone URL`__ beacon:

Expand Down
82 changes: 82 additions & 0 deletions scapy/contrib/altbeacon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# -*- mode: python3; indent-tabs-mode: nil; tab-width: 4 -*-
# altbeacon.py - protocol handlers for AltBeacon
#
# This file is part of Scapy
# See http://www.secdev.org/projects/scapy for more information
# Copyright (C) Michael Farrell <[email protected]>
# This program is published under a GPLv2 (or later) license
#
# scapy.contrib.description = AltBeacon BLE proximity beacon
# scapy.contrib.status = loads
"""
scapy.contrib.altbeacon - AltBeacon Bluetooth LE proximity beacons.

The AltBeacon specification can be found at: https://github.com/AltBeacon/spec
"""

from scapy.fields import ByteField, ShortField, SignedByteField, \
StrFixedLenField
from scapy.layers.bluetooth import EIR_Hdr, EIR_Manufacturer_Specific_Data, \
UUIDField, LowEnergyBeaconHelper
from scapy.packet import Packet


# When building beacon frames, one should use their own manufacturer ID.
#
# However, most software (including the AltBeacon SDK) requires explicitly
# registering particular manufacturer IDs to listen to, and the only ID used is
# that of Radius Networks (the developer of the specification).
#
# To maximise compatibility, Scapy's implementation of
# LowEnergyBeaconHelper.build_eir (for constructing frames) uses Radius
# Networks' manufacturer ID.
#
# Scapy's implementation of AltBeacon **does not** require a specific
# manufacturer ID to detect AltBeacons - it uses
# EIR_Manufacturer_Specific_Data.register_magic_payload.
RADIUS_NETWORKS_MFG = 0x0118


class AltBeacon(Packet, LowEnergyBeaconHelper):
"""
AltBeacon broadcast frame type.

https://github.com/AltBeacon/spec
"""
name = "AltBeacon"
magic = b"\xBE\xAC"
fields_desc = [
StrFixedLenField("header", magic, len(magic)),

# The spec says this is 20 bytes, with >=16 bytes being an
# organisational unit-specific identifier. However, the Android library
# treats this as UUID + uint16 + uint16.
UUIDField("id1", None),

# Local identifier
ShortField("id2", None),
ShortField("id3", None),

SignedByteField("tx_power", None),
ByteField("mfg_reserved", None),
]

@classmethod
def magic_check(cls, payload):
"""
Checks if the given payload is for us (starts with our magic string).
"""
return payload.startswith(cls.magic)

def build_eir(self):
"""Builds a list of EIR messages to wrap this frame."""

# Note: Company ID is not required by spec, but most tools only look
# for manufacturer-specific data with Radius Networks' manufacturer ID.
return LowEnergyBeaconHelper.base_eir + [
EIR_Hdr() / EIR_Manufacturer_Specific_Data(
company_id=RADIUS_NETWORKS_MFG) / self
]


EIR_Manufacturer_Specific_Data.register_magic_payload(AltBeacon)
94 changes: 94 additions & 0 deletions scapy/contrib/altbeacon.uts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
% AltBeacon unit tests
#
# Type the following command to launch start the tests:
# $ test/run_tests -P "load_contrib('altbeacon')" -t scapy/contrib/altbeacon.uts
#
# AltBeaconParser tests adapted from:
# https://github.com/AltBeacon/android-beacon-library/blob/master/lib/src/test/java/org/altbeacon/beacon/AltBeaconParserTest.java

+ AltBeacon tests

= Setup

def next_eir(p):
return EIR_Hdr(p[Padding])

= Presence check

AltBeacon

= AltBeaconParserTest.testRecognizeBeacon

d = hex_bytes('02011a1bff1801beac2f234454cf6d4a0fadf2f4911ba9ffa600010002c509')
p = EIR_Hdr(d)

# First is a flags header
assert EIR_Flags in p

# Then the AltBeacon
p = next_eir(p)
assert p[EIR_Manufacturer_Specific_Data].company_id == RADIUS_NETWORKS_MFG
assert p[AltBeacon].mfg_reserved == 9


= AltBeaconParserTest.testDetectsDaveMHardwareBeacon

d = hex_bytes('02011a1bff1801beac2f234454cf6d4a0fadf2f4911ba9ffa600050003be020e09526164426561636f6e20555342020a03000000000000000000000000')
p = EIR_Hdr(d)

# First is Flags
assert EIR_Flags in p

# Then the AltBeacon
p = next_eir(p)
assert p[EIR_Manufacturer_Specific_Data].company_id == RADIUS_NETWORKS_MFG
assert AltBeacon in p

# Then CompleteLocalName
p = next_eir(p)
assert p[EIR_CompleteLocalName].local_name == b'RadBeacon USB'

# Then TX_Power_Level
p = next_eir(p)
assert p[EIR_TX_Power_Level].level == 3

= AltBeaconParserTest.testParseWrongFormatReturnsNothing

d = hex_bytes('02011a1aff1801ffff2f234454cf6d4a0fadf2f4911ba9ffa600010002c509')
p = EIR_Hdr(d)

# First is Flags
assert EIR_Flags in p

# Then the EIR_Manufacturer_Specific_Data
p = next_eir(p)
assert p[EIR_Manufacturer_Specific_Data].company_id == RADIUS_NETWORKS_MFG
assert AltBeacon not in p

= AltBeaconParserTest.testParsesBeaconMissingDataField

d = hex_bytes('02011a1aff1801beac2f234454cf6d4a0fadf2f4911ba9ffa600010002c50000')
p = EIR_Hdr(d)

# First is Flags
assert EIR_Flags in p

# Then the EIR_Manufacturer_Specific_Data
p = next_eir(p)
assert p[EIR_Manufacturer_Specific_Data].company_id == RADIUS_NETWORKS_MFG
assert p[AltBeacon].id1 == uuid.UUID('2f234454-cf6d-4a0f-adf2-f4911ba9ffa6')
assert p[AltBeacon].id2 == 1
assert p[AltBeacon].id3 == 2
assert p[AltBeacon].tx_power == -59

= Build EIR

p = AltBeacon(
id1=uuid.UUID('2f234454-cf6d-4a0f-adf2-f4911ba9ffa6'),
id2=1,
id3=2,
tx_power=-59,
)

d = raw(p.build_eir()[-1])
assert d == hex_bytes('1bff1801beac2f234454cf6d4a0fadf2f4911ba9ffa600010002c500')
92 changes: 56 additions & 36 deletions scapy/contrib/eddystone.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,29 @@
# Copyright (C) Michael Farrell <[email protected]>
# This program is published under a GPLv2 (or later) license
#
# The Eddystone specification can be found at:
# https://github.com/google/eddystone/blob/master/protocol-specification.md
#
# scapy.contrib.description = Eddystone BLE proximity beacon
# scapy.contrib.status = loads
"""
scapy.contrib.eddystone - Google Eddystone Bluetooth LE proximity beacons.

The Eddystone specification can be found at:
https://github.com/google/eddystone/blob/master/protocol-specification.md

These beacons are used as building blocks for other systems:

* Google's Physical Web <https://google.github.io/physical-web/>
* RuuviTag <https://github.com/ruuvi/ruuvi-sensor-protocols>
* Waze Beacons <https://www.waze.com/beacons>

"""

from scapy.compat import orb
from scapy.packet import bind_layers, Packet
from scapy.fields import IntField, SignedByteField, StrField, BitField, \
StrFixedLenField, ShortField, FixedPointField, ByteEnumField
from scapy.layers.bluetooth import EIR_Hdr, EIR_ServiceData16BitUUID, \
EIR_Flags, EIR_CompleteList16BitServiceUUIDs, HCI_Hdr, HCI_Command_Hdr, \
HCI_Cmd_LE_Set_Advertising_Data, HCI_LE_Meta_Advertising_Report
EIR_CompleteList16BitServiceUUIDs, LowEnergyBeaconHelper
from scapy.modules import six
from scapy.packet import bind_layers, Packet

EDDYSTONE_UUID = 0xfeaa

Expand Down Expand Up @@ -93,8 +102,12 @@ def any2i(self, pkt, x):
return x


class Eddystone_Frame(Packet):
# https://github.com/google/eddystone/blob/master/protocol-specification.md
class Eddystone_Frame(Packet, LowEnergyBeaconHelper):
"""
The base Eddystone frame on which all Eddystone messages are built.

https://github.com/google/eddystone/blob/master/protocol-specification.md
"""
name = "Eddystone Frame"
fields_desc = [
BitField("type", None, 4),
Expand All @@ -104,36 +117,19 @@ class Eddystone_Frame(Packet):
def build_eir(self):
"""Builds a list of EIR messages to wrap this frame."""

return [
EIR_Hdr() / EIR_Flags(flags=[
"general_disc_mode", "br_edr_not_supported"]),
return LowEnergyBeaconHelper.base_eir + [
EIR_Hdr() / EIR_CompleteList16BitServiceUUIDs(svc_uuids=[
EDDYSTONE_UUID]),
EIR_Hdr() / EIR_ServiceData16BitUUID() / self
]

def build_advertising_report(self):
"""Builds HCI_LE_Meta_Advertising_Report containing this frame."""

return HCI_LE_Meta_Advertising_Report(
type=0, # Undirected
atype=1, # Random address
data=self.build_eir()
)

def build_set_advertising_data(self):
"""Builds a HCI_Cmd_LE_Set_Advertising_Data containing this frame.

This includes the HCI_Hdr and HCI_Command_Hdr layers.
"""

return HCI_Hdr() / HCI_Command_Hdr() / HCI_Cmd_LE_Set_Advertising_Data(
data=self.build_eir()
)


class Eddystone_UID(Packet):
# https://github.com/google/eddystone/tree/master/eddystone-uid
"""
An Eddystone type for transmitting a unique identifier.

https://github.com/google/eddystone/tree/master/eddystone-uid
"""
name = "Eddystone UID"
fields_desc = [
SignedByteField("tx_power", 0),
Expand All @@ -144,7 +140,11 @@ class Eddystone_UID(Packet):


class Eddystone_URL(Packet):
# https://github.com/google/eddystone/tree/master/eddystone-url
"""
An Eddystone type for transmitting a URL (to a web page).

https://github.com/google/eddystone/tree/master/eddystone-url
"""
name = "Eddystone URL"
fields_desc = [
SignedByteField("tx_power", 0),
Expand Down Expand Up @@ -174,7 +174,11 @@ def from_url(url):


class Eddystone_TLM(Packet):
# https://github.com/google/eddystone/tree/master/eddystone-tlm
"""
An Eddystone type for transmitting beacon telemetry information.

https://github.com/google/eddystone/tree/master/eddystone-tlm
"""
name = "Eddystone TLM"
fields_desc = [
ByteEnumField("version", None, {
Expand All @@ -185,7 +189,11 @@ class Eddystone_TLM(Packet):


class Eddystone_TLM_Unencrypted(Packet):
# https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-plain.md
"""
A subtype of Eddystone-TLM for transmitting telemetry in unencrypted form.

https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-plain.md
"""
name = "Eddystone TLM (Unencrypted)"
fields_desc = [
ShortField("batt_mv", 0),
Expand All @@ -196,7 +204,13 @@ class Eddystone_TLM_Unencrypted(Packet):


class Eddystone_TLM_Encrypted(Packet):
# https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-encrypted.md
"""
A subtype of Eddystone-TLM for transmitting telemetry in encrypted form.

This implementation does not support decrypting this data.

https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-encrypted.md
"""
name = "Eddystone TLM (Encrypted)"
fields_desc = [
StrFixedLenField("etlm", None, 12),
Expand All @@ -206,7 +220,13 @@ class Eddystone_TLM_Encrypted(Packet):


class Eddystone_EID(Packet):
# https://github.com/google/eddystone/tree/master/eddystone-eid
"""
An Eddystone type for transmitting encrypted, ephemeral identifiers.

This implementation does not support decrypting this data.

https://github.com/google/eddystone/tree/master/eddystone-eid
"""
name = "Eddystone EID"
fields_desc = [
SignedByteField("tx_power", 0),
Expand Down
Loading