Skip to content

Commit

Permalink
Added ATT error handling for responses in ATTConnection
Browse files Browse the repository at this point in the history
  • Loading branch information
colemancda committed Dec 3, 2017
1 parent d1b8a91 commit 84cd38d
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 39 deletions.
91 changes: 66 additions & 25 deletions Sources/BluetoothLinux/ATTConnection.swift
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ internal final class ATTConnection {
guard let sendOperation = pickNextSendOpcode()
else { return false }


assert(sendOperation.data.count <= maximumTransmissionUnit, "Trying to send \(sendOperation.data.count) bytes when MTU is \(maximumTransmissionUnit)")

//print("Sending data... (\(sendOpcode.data.count) bytes)")
Expand Down Expand Up @@ -215,7 +216,7 @@ internal final class ATTConnection {
///
/// - Returns: Identifier of queued send operation or `nil` if the PDU cannot be sent.
public func send <PDU: ATTProtocolDataUnit> (_ pdu: PDU,
response: (callback: (ATTProtocolDataUnit) -> (), ATTProtocolDataUnit.Type)? = nil) -> UInt? {
response: (callback: (AnyResponse) -> (), ATTProtocolDataUnit.Type)? = nil) -> UInt? {

let attributeOpcode = PDU.attributeOpcode

Expand Down Expand Up @@ -345,15 +346,8 @@ internal final class ATTConnection {
throw Error.UnexpectedResponse(data)
}

// attempt to deserialize
guard let responseInfo = sendOperation.response
else { fatalError("Response was expected, but no callback was provided") }

guard let responsePDU = responseInfo.responseType.init(byteValue: Array(data))
else { throw Error.GarbageResponse(data) }

// success!
responseInfo.callback(responsePDU)
try sendOperation.handle(data: data)

//wakeup_writer(att);
}
Expand All @@ -366,19 +360,8 @@ internal final class ATTConnection {

self.pendingIndication = nil

// not necesary
//guard data.count == 1
// else { throw Error.GarbageResponse(data) }

// attempt to deserialize
guard let responseInfo = sendOperation.response
else { fatalError("Response was expected, but no callback was provided") }

guard let responsePDU = responseInfo.responseType.init(byteValue: Array(data))
else { throw Error.GarbageResponse(data) }

// success!
responseInfo.callback(responsePDU)
try sendOperation.handle(data: data)

//wakeup_writer(att);
}
Expand Down Expand Up @@ -472,7 +455,7 @@ internal final class ATTConnection {
return sendOpcode
}

// There is either a request pending or no requests queued.
// There is either a request pending or no requests queued.
// If there is no pending indication, pick an operation from the indication queue.
if pendingIndication == nil,
let sendOpcode = indicationQueue.popFirst() {
Expand Down Expand Up @@ -538,9 +521,43 @@ public enum ATTConnectionError: Error {
case UnexpectedResponse(Data)
}

internal extension ATTConnection {

typealias AnyResponse = AnyATTResponse
}

public enum AnyATTResponse {

case error(ATTErrorResponse)
case value(ATTProtocolDataUnit)
}

public enum ATTResponse <Value: ATTProtocolDataUnit> {

case error(ATTErrorResponse)
case value(Value)

internal init(_ anyResponse: AnyATTResponse) {

// validate types
assert(Value.self != ATTErrorResponse.self)
assert(Value.attributeOpcode.type == .Response)

switch anyResponse {
case let .error(error):
self = .error(error)
case let .value(value):
let specializedValue = value as! Value
self = .value(specializedValue)
}
}
}

// MARK: - Private Supporting Types

private final class ATTSendOperation {
fileprivate final class ATTSendOperation {

typealias Response = ATTConnection.AnyResponse

/// The operation identifier.
let identifier: UInt
Expand All @@ -552,18 +569,42 @@ private final class ATTSendOperation {
let opcode: ATTOpcode

/// The response callback.
let response: (callback: (ATTProtocolDataUnit) -> (), responseType: ATTProtocolDataUnit.Type)?
let response: (callback: (Response) -> (), responseType: ATTProtocolDataUnit.Type)?

fileprivate init(identifier: UInt,
opcode: ATT.Opcode,
data: [UInt8],
response: (callback: (ATTProtocolDataUnit) -> (), responseType: ATTProtocolDataUnit.Type)? = nil) {
response: (callback: (Response) -> (), responseType: ATTProtocolDataUnit.Type)? = nil) {

self.identifier = identifier
self.opcode = opcode
self.data = data
self.response = response
}

func handle(data: Data) throws {

guard let responseInfo = self.response
else { throw ATTConnectionError.UnexpectedResponse(data) }

guard let opcode = data.first
else { throw ATTConnectionError.GarbageResponse(data) }

if opcode == ATT.Opcode.ErrorResponse.rawValue {

guard let errorResponse = ATTErrorResponse(byteValue: [UInt8](data))
else { throw ATTConnectionError.GarbageResponse(data) }

responseInfo.callback(.error(errorResponse))

} else {

guard let response = responseInfo.responseType.init(byteValue: [UInt8](data))
else { throw ATTConnectionError.GarbageResponse(data) }

responseInfo.callback(.value(response))
}
}
}

private protocol ATTNotifyType {
Expand Down
56 changes: 43 additions & 13 deletions Sources/BluetoothLinux/GATTClient.swift
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ public final class GATTClient {
}

public init(socket: L2CAPSocket,
maximumTransmissionUnit: Int = ATT.MTU.LowEnergy.Default) {
maximumTransmissionUnit: Int = ATT.MTU.LowEnergy.Default,
log: ((String) -> ())? = nil) {

self.connection = ATTConnection(socket: socket)
self.connection.maximumTransmissionUnit = maximumTransmissionUnit
self.log = log
self.registerATTHandlers()

// queue MTU exchange
Expand Down Expand Up @@ -61,7 +63,7 @@ public final class GATTClient {
/// Discover All Primary Services
///
/// This sub-procedure is used by a client to discover all the primary services on a server.
public func discoverAllPrimaryServices() {
public func discoverAllPrimaryServices(_ completion: () -> ()) {

/// The Attribute Protocol Read By Group Type Request shall be used with
/// the Attribute Type parameter set to the UUID for «Primary Service».
Expand All @@ -74,18 +76,16 @@ public final class GATTClient {
@inline(__always)
private func registerATTHandlers() {

// Exchange MTU
//let _ = connection.register(exchangeMTU)

// value confirmation

}

@inline(__always)
private func send <Request: ATTProtocolDataUnit, Response: ATTProtocolDataUnit> (_ request: Request, response: @escaping (Response) -> ()) {
private func send <Request: ATTProtocolDataUnit, Response: ATTProtocolDataUnit> (_ request: Request, response: @escaping (ATTResponse<Response>) -> ()) {

log?("Request: \(request)")

let callback: (ATTProtocolDataUnit) -> () = { response($0 as! Response) }
let callback: (AnyATTResponse) -> () = { response(ATTResponse<Response>($0)) }

let responseType: ATTProtocolDataUnit.Type = Response.self

Expand All @@ -101,8 +101,7 @@ public final class GATTClient {

let pdu = ATTMaximumTransmissionUnitRequest(clientMTU: clientMTU)



send(pdu, response: exchangeMTUResponse)
}

private func discoverServices(uuid: BluetoothUUID? = nil,
Expand Down Expand Up @@ -138,7 +137,27 @@ public final class GATTClient {

// MARK: - Callbacks

private func readByGroupType(pdu: ATTReadByGroupTypeResponse) {
private func exchangeMTUResponse(_ response: ATTResponse<ATTMaximumTranssmissionUnitResponse>) {

switch response {

case let .error(error):

print(error)

case let .value(pdu):

let finalMTU = Int(pdu.serverMTU)

let currentMTU = self.connection.maximumTransmissionUnit

log?("MTU Exchange (\(currentMTU) -> \(finalMTU))")

self.connection.maximumTransmissionUnit = finalMTU
}
}

private func readByGroupType(_ response: ATTResponse<ATTReadByGroupTypeResponse>) {

// Read By Group Type Response returns a list of Attribute Handle, End Group Handle, and Attribute Value tuples
// corresponding to the services supported by the server. Each Attribute Value contained in the response is the
Expand All @@ -147,12 +166,23 @@ public final class GATTClient {
// The Read By Group Type Request shall be called again with the Starting Handle set to one greater than the
// last End Group Handle in the Read By Group Type Response.

let lastEnd = pdu.data.last?.endGroupHandle ?? 0x00
switch response {

case let .error(error):

print(error)

case let .value(pdu):

let lastEnd = pdu.data.last?.endGroupHandle ?? 0x00

print(pdu)
}


print(pdu)
}

private func findByType(pdu: ATTFindByTypeResponse) {
private func findByType(_ response: ATTResponse<ATTFindByTypeResponse>) {


}
Expand Down
2 changes: 1 addition & 1 deletion Sources/GATTClientTest/GATTClientTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func GATTClientTest(adapter: Adapter, address: Address) {

// queue operations

client.discoverAllPrimaryServices()
client.discoverAllPrimaryServices { print($0) }

func didFinish() -> Bool {

Expand Down

0 comments on commit 84cd38d

Please sign in to comment.