|
| 1 | +// |
| 2 | +// Plugin.swift |
| 3 | +// IBeacon |
| 4 | +// |
| 5 | +// Decodes Apple iBeacon advertisements from manufacturer-specific data. |
| 6 | +// |
| 7 | +// Payload layout (after the company identifier, which the host strips into the envelope header): |
| 8 | +// [0] type = 0x02 |
| 9 | +// [1] length = 0x15 (21) |
| 10 | +// [2..17] proximity UUID, big-endian |
| 11 | +// [18..19] major, big-endian |
| 12 | +// [20..21] minor, big-endian |
| 13 | +// [22] measured power at 1m, signed |
| 14 | +// |
| 15 | +// Field keys and labels intentionally match NativeIBeaconParser so the two can be diffed. |
| 16 | +// |
| 17 | + |
| 18 | +import BLEPluginSDK |
| 19 | + |
| 20 | +private let appleCompanyIdentifier: UInt16 = 0x004C |
| 21 | +private let iBeaconType: UInt8 = 0x02 |
| 22 | +private let iBeaconLength: UInt8 = 0x15 |
| 23 | + |
| 24 | +func parseManufacturerData(_ input: ParseInput) -> Fields? { |
| 25 | + guard input.companyIdentifier == appleCompanyIdentifier else { return nil } |
| 26 | + |
| 27 | + var payload = input.payload |
| 28 | + // Apple ships several manufacturer-data formats under this company id; only 0x02/0x15 is |
| 29 | + // an iBeacon. Anything else is declined so other parsers (or the raw view) can handle it. |
| 30 | + guard payload.remaining == 23, |
| 31 | + payload.readUInt8() == iBeaconType, |
| 32 | + payload.readUInt8() == iBeaconLength, |
| 33 | + let uuid = payload.readUUID(), |
| 34 | + let major = payload.readUInt16BigEndian(), |
| 35 | + let minor = payload.readUInt16BigEndian(), |
| 36 | + let measuredPower = payload.readInt8() |
| 37 | + else { return nil } |
| 38 | + |
| 39 | + var fields = Fields(summary: "iBeacon") |
| 40 | + fields.uuid("uuid", label: "Proximity UUID", uuid) |
| 41 | + fields.uint("major", label: "Major", UInt64(major)) |
| 42 | + fields.uint("minor", label: "Minor", UInt64(minor)) |
| 43 | + fields.int("tx_power", label: "Measured Power", Int64(measuredPower), unit: "dBm") |
| 44 | + return fields |
| 45 | +} |
0 commit comments