Skip to content
Open
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
2 changes: 2 additions & 0 deletions Library/Classes/Implementation/DFUServiceDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ internal enum DFURemoteError : Int {
/// Error raised when the CRC reported by the remote device does not match.
/// Service has done 3 attempts to send the data.
case crcError = 309
/// The requested advertising name is too long for the current ATT MTU.
case invalidAdvertisementName = 310
/// The service went into an invalid state. The service will try to close
/// without crashing. Recovery to a know state is not possible.
case invalidInternalState = 500
Expand Down
14 changes: 11 additions & 3 deletions Library/Classes/Implementation/DFUServiceInitiator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -280,9 +280,17 @@ import CoreBluetooth

If ``alternativeAdvertisingNameEnabled`` is `true` then this specifies the
alternative name to use. If `nil` (default) then a random name is generated.

The maximum length of the alternative advertising name is 20 bytes.
Longer name will be truncated. UTF-8 characters can be cut in the middle.

The maximum length of the alternative advertising name is 18 UTF-8 bytes.
This is required to keep the Set Name request (1-byte op code, 1-byte
length and the name itself) within the 20-byte payload of the default
ATT MTU. CoreBluetooth always reports the maximum write-with-response
length as 512 once Long Write is supported, so the limit cannot be
derived dynamically and a longer name would fall back to ATT Prepare
Write, which Nordic's buttonless DFU service does not handle.

The library validates the name before writing it to the device and fails
with ``DFUError/invalidAdvertisementName`` if it is too long.
*/
@objc public var alternativeAdvertisingName: String? = nil

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,19 @@ extension ButtonlessDFUResultCode : CustomStringConvertible {
internal enum ButtonlessDFURequest {
case enterBootloader
case set(name: String)


/// Maximum length in bytes of the UTF-8 encoded advertising name accepted
/// by the Set Name request.
///
/// The Set Name request is composed of a 1-byte op code, a 1-byte length
/// field and the UTF-8 encoded name. With the default ATT MTU of 23 bytes,
/// the maximum write payload is 20 bytes, leaving 18 bytes for the name
/// itself. CoreBluetooth always reports `maximumWriteValueLength(for: .withResponse)`
/// as 512 when Long Write is supported, so the limit cannot be derived
/// dynamically and is hard-coded here to avoid a fallback to ATT Prepare
/// Write, which Nordic's buttonless DFU service does not handle.
static let maximumAdvertisingNameLength = 18

var data: Data {
switch self {
case .enterBootloader:
Expand Down Expand Up @@ -270,7 +282,15 @@ internal class ButtonlessDFU : NSObject, CBPeripheralDelegate, DFUCharacteristic
peripheral.delegate = self

let buttonlessUUID = characteristic.uuid.uuidString

if case .set(let name) = request,
name.lengthOfBytes(using: .utf8) > ButtonlessDFURequest.maximumAdvertisingNameLength {
let maximum = ButtonlessDFURequest.maximumAdvertisingNameLength
logger.e("\(request) exceeds maximum advertising name length \(maximum)")
report?(.invalidAdvertisementName,
"Alternative advertising name is too long. Maximum length is \(maximum) bytes.")
return
}

logger.v("Writing to characteristic \(buttonlessUUID)...")
logger.d("peripheral.writeValue(0x\(request.data.hexString), for: \(buttonlessUUID), type: .withResponse)")
peripheral.writeValue(request.data, for: characteristic, type: .withResponse)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ internal class SecureDFUPeripheral : BaseCommonDFUPeripheral<SecureDFUExecutor,

override init(_ initiator: DFUServiceInitiator, _ logger: LoggerHelper) {
self.alternativeAdvertisingNameEnabled = initiator.alternativeAdvertisingNameEnabled
self.alternativeAdvertisingName = initiator.alternativeAdvertisingName.map { String($0.prefix(20)) }
self.alternativeAdvertisingName = initiator.alternativeAdvertisingName
super.init(initiator, logger)
}

Expand Down