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
10 changes: 8 additions & 2 deletions Library/Classes/Implementation/DFUServiceInitiator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,14 @@ 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 depends on the
current ATT MTU, and can be up to 20 bytes. To work with the default
ATT MTU, keep the name to 18 UTF-8 bytes or fewer. Longer names may
work after a larger ATT MTU has been negotiated.

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

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


private static let setNameRequestOverhead = 2
private static let maximumAdvertisingNameLength = 20

var data: Data {
switch self {
case .enterBootloader:
Expand All @@ -111,6 +114,28 @@ internal enum ButtonlessDFURequest {
return data
}
}

func maximumPayloadLength(for peripheral: CBPeripheral) -> Int {
let writePayloadLength = peripheral.maximumWriteValueLength(for: .withResponse)
switch self {
case .enterBootloader:
return writePayloadLength
case .set:
return min(
Self.maximumAdvertisingNameLength,
max(0, writePayloadLength - Self.setNameRequestOverhead)
)
}
}

var payloadLength: Int {
switch self {
case .enterBootloader:
return data.count
case .set(let name):
return name.lengthOfBytes(using: .utf8)
}
}
}

extension ButtonlessDFURequest : CustomStringConvertible {
Expand Down Expand Up @@ -270,6 +295,13 @@ internal class ButtonlessDFU : NSObject, CBPeripheralDelegate, DFUCharacteristic
peripheral.delegate = self

let buttonlessUUID = characteristic.uuid.uuidString
let maximumPayloadLength = request.maximumPayloadLength(for: peripheral)
guard request.payloadLength <= maximumPayloadLength else {
logger.e("\(request) exceeds maximum payload length \(maximumPayloadLength)")
report?(.invalidAdvertisementName,
"Alternative advertising name is too long. Maximum length is \(maximumPayloadLength) bytes.")
return
}

logger.v("Writing to characteristic \(buttonlessUUID)...")
logger.d("peripheral.writeValue(0x\(request.data.hexString), for: \(buttonlessUUID), 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