FakeCuttlefish.swift [plain text]
//
// FakeCuttlefish.swift
// Security
//
// Created by Ben Williamson on 5/23/18.
//
import CloudKitCode
import Foundation
enum FakeCuttlefishError: Error {
case notEmpty
case unknownChangeToken
case unknownPeerID
}
enum FakeCuttlefishOpinion {
case trusts
case trustsByPreapproval
case excludes
}
struct FakeCuttlefishAssertion: CustomStringConvertible {
let peer: String
let opinion: FakeCuttlefishOpinion
let target: String
func check(peer: Peer?, target: Peer?) -> Bool {
guard let peer = peer else {
return false
}
guard peer.hasDynamicInfoAndSig else {
// No opinions? You've failed this assertion.
return false
}
let dynamicInfo = TPPeerDynamicInfo(data: peer.dynamicInfoAndSig.peerDynamicInfo, sig: peer.dynamicInfoAndSig.sig)
guard let realDynamicInfo = dynamicInfo else {
return false
}
let targetPermanentInfo: TPPeerPermanentInfo? =
target != nil ? TPPeerPermanentInfo(peerID: self.target,
data: target!.permanentInfoAndSig.peerPermanentInfo,
sig: target!.permanentInfoAndSig.sig,
keyFactory: TPECPublicKeyFactory())
: nil
switch self.opinion {
case .trusts:
return realDynamicInfo.includedPeerIDs.contains(self.target)
case .trustsByPreapproval:
guard let pubSignSPKI = targetPermanentInfo?.signingPubKey.spki() else {
return false
}
let hash = TPHashBuilder.hash(with: .SHA256, of: pubSignSPKI)
return realDynamicInfo.preapprovals.contains(hash)
case .excludes:
return realDynamicInfo.excludedPeerIDs.contains(self.target)
}
}
var description: String {
return "DCA:(\(self.peer)\(self.opinion)\(self.target))"
}
}
@objc class FakeCuttlefishNotify: NSObject {
let pushes: (Data) -> Void
let containerName: String
@objc init(_ containerName: String, pushes: @escaping (Data) -> Void) {
self.containerName = containerName
self.pushes = pushes
}
@objc public func notify(_ function: String) throws {
let notification: [String: Dictionary<String, Any>] = [
"aps": ["content-available": 1],
"cf": [
"f": function,
"c": self.containerName,
],
]
let payload: Data
do {
payload = try JSONSerialization.data(withJSONObject: notification)
} catch {
throw error
}
self.pushes(payload)
}
}
extension ViewKey {
func fakeRecord(zoneID: CKRecordZone.ID) -> CKRecord {
let recordID = CKRecord.ID(__recordName: self.uuid, zoneID: zoneID)
let record = CKRecord(recordType: SecCKRecordIntermediateKeyType, recordID: recordID)
record[SecCKRecordWrappedKeyKey] = self.wrappedkeyBase64
switch(self.keyclass) {
case .tlk:
record[SecCKRecordKeyClassKey] = "tlk"
case .classA:
record[SecCKRecordKeyClassKey] = "classA"
case .classC:
record[SecCKRecordKeyClassKey] = "classC"
case .UNRECOGNIZED:
abort()
}
if self.parentkeyUuid.count > 0 {
// TODO: no idea how to tell it about the 'verify' action
record[SecCKRecordParentKeyRefKey] = CKRecord.Reference(recordID: CKRecord.ID(__recordName: self.parentkeyUuid, zoneID: zoneID), action: .none)
}
return record
}
func fakeKeyPointer(zoneID: CKRecordZone.ID) -> CKRecord {
let recordName: String
switch(self.keyclass) {
case .tlk:
recordName = "tlk"
case .classA:
recordName = "classA"
case .classC:
recordName = "classC"
case .UNRECOGNIZED:
abort()
}
let recordID = CKRecord.ID(__recordName: recordName, zoneID: zoneID)
let record = CKRecord(recordType: SecCKRecordCurrentKeyType, recordID: recordID)
// TODO: no idea how to tell it about the 'verify' action
record[SecCKRecordParentKeyRefKey] = CKRecord.Reference(recordID: CKRecord.ID(__recordName: self.uuid, zoneID: zoneID), action: .none)
return record
}
}
extension TLKShare {
func fakeRecord(zoneID: CKRecordZone.ID) -> CKRecord {
let recordID = CKRecord.ID(__recordName: "tlkshare-\(self.keyUuid)::\(self.receiver)::\(self.sender)", zoneID: zoneID)
let record = CKRecord(recordType: SecCKRecordTLKShareType, recordID: recordID)
record[SecCKRecordSenderPeerID] = self.sender
record[SecCKRecordReceiverPeerID] = self.receiver
record[SecCKRecordReceiverPublicEncryptionKey] = self.receiverPublicEncryptionKey
record[SecCKRecordCurve] = self.curve
record[SecCKRecordVersion] = self.version
record[SecCKRecordEpoch] = self.epoch
record[SecCKRecordPoisoned] = self.poisoned
// TODO: no idea how to tell it about the 'verify' action
record[SecCKRecordParentKeyRefKey] = CKRecord.Reference(recordID: CKRecord.ID(__recordName: self.keyUuid, zoneID: zoneID), action: .none)
record[SecCKRecordWrappedKeyKey] = self.wrappedkey
record[SecCKRecordSignature] = self.signature
return record
}
}
class FakeCuttlefishServer: CuttlefishAPIAsync {
struct State {
var peersByID: [String: Peer] = [:]
var recoverySigningPubKey: Data?
var recoveryEncryptionPubKey: Data?
var bottles: [Bottle] = []
var viewKeys: [CKRecordZone.ID: ViewKeys] = [:]
var tlkShares: [CKRecordZone.ID: [TLKShare]] = [:]
init() {
}
}
var state = State()
var snapshotsByChangeToken: [String: State] = [:]
var currentChange: Int = 0
var currentChangeToken: String = ""
let notify: FakeCuttlefishNotify?
//var fakeCKZones: [CKRecordZone.ID: FakeCKZone]
var fakeCKZones: NSMutableDictionary
// @property (nullable) NSMutableDictionary<CKRecordZoneID*, ZoneKeys*>* keys;
var ckksZoneKeys: NSMutableDictionary
var nextFetchErrors: [Error] = []
var fetchViableBottlesError: [Error] = []
var nextJoinErrors: [Error] = []
var nextUpdateTrustErrors: [Error] = []
var returnNoActionResponse: Bool = false
var returnRepairAccountResponse: Bool = false
var returnRepairEscrowResponse: Bool = false
var returnResetOctagonResponse: Bool = false
var returnRepairErrorResponse: Error?
var fetchChangesCalledCount: Int = 0
var nextEstablishReturnsMoreChanges: Bool = false
var establishListener: ((EstablishRequest) -> NSError?)?
var updateListener: ((UpdateTrustRequest) -> NSError?)?
var fetchChangesListener: ((FetchChangesRequest) -> NSError?)?
var joinListener: ((JoinWithVoucherRequest) -> NSError?)?
var healthListener: ((GetRepairActionRequest) -> NSError?)?
var fetchViableBottlesListener: ((FetchViableBottlesRequest) -> NSError?)?
var fetchViableBottlesDontReturnBottleWithID: String?
init(_ notify: FakeCuttlefishNotify?, ckZones: NSMutableDictionary, ckksZoneKeys: NSMutableDictionary) {
self.notify = notify
self.fakeCKZones = ckZones
self.ckksZoneKeys = ckksZoneKeys
}
func deleteAllPeers() {
self.state.peersByID.removeAll()
self.makeSnapshot()
}
func pushNotify(_ function: String) {
if let notify = self.notify {
do {
try notify.notify(function)
} catch {
}
}
}
static func makeCloudKitCuttlefishError(code: CuttlefishErrorCode) -> NSError {
return CKPrettyError(domain: CKInternalErrorDomain,
code: CKInternalErrorCode.errorInternalPluginError.rawValue,
userInfo: [NSUnderlyingErrorKey: NSError(domain: CuttlefishErrorDomain,
code: code.rawValue,
userInfo: nil)])
}
func makeSnapshot() {
self.currentChange += 1
self.currentChangeToken = "change\(self.currentChange)"
self.snapshotsByChangeToken[self.currentChangeToken] = self.state
}
func changesSince(snapshot: State) -> Changes {
return Changes.with { changes in
changes.changeToken = self.currentChangeToken
changes.differences = self.state.peersByID.compactMap({ (key: String, value: Peer) -> PeerDifference? in
let old = snapshot.peersByID[key]
if old == nil {
return PeerDifference.with {
$0.add = value
}
} else if old != value {
return PeerDifference.with {
$0.update = value
}
} else {
return nil
}
})
snapshot.peersByID.forEach { (key: String, _: Peer) in
if nil == self.state.peersByID[key] {
changes.differences.append(PeerDifference.with {
$0.remove = Peer.with {
$0.peerID = key
}
})
}
}
if self.state.recoverySigningPubKey != snapshot.recoverySigningPubKey {
changes.recoverySigningPubKey = self.state.recoverySigningPubKey ?? Data()
}
if self.state.recoveryEncryptionPubKey != snapshot.recoveryEncryptionPubKey {
changes.recoveryEncryptionPubKey = self.state.recoveryEncryptionPubKey ?? Data()
}
}
}
func reset(_ request: ResetRequest, completion: @escaping (ResetResponse?, Error?) -> Void) {
print("FakeCuttlefish: reset called")
self.state = State()
self.makeSnapshot()
completion(ResetResponse.with {
$0.changes = self.changesSince(snapshot: State())
}, nil)
self.pushNotify("reset")
}
func newKeysConflict(viewKeys: [ViewKeys]) -> Bool {
#if OCTAGON_TEST_FILL_ZONEKEYS
for keys in viewKeys {
let rzid = CKRecordZone.ID(zoneName: keys.view)
if let currentViewKeys = self.ckksZoneKeys[rzid] as? CKKSCurrentKeySet {
// Uploading the current view keys is okay. Fail only if they don't match
if keys.newTlk.uuid != currentViewKeys.tlk!.uuid ||
keys.newClassA.uuid != currentViewKeys.classA!.uuid ||
keys.newClassC.uuid != currentViewKeys.classC!.uuid {
return true
}
}
}
#endif
return false
}
func store(viewKeys: [ViewKeys]) -> [CKRecord] {
var allRecords: [CKRecord] = []
viewKeys.forEach { viewKeys in
let rzid = CKRecordZone.ID(zoneName: viewKeys.view)
self.state.viewKeys[rzid] = viewKeys
// Real cuttlefish makes these zones for you
if self.fakeCKZones[rzid] == nil {
self.fakeCKZones[rzid] = FakeCKZone(zone: rzid)
}
if let fakeZone = self.fakeCKZones[rzid] as? FakeCKZone {
fakeZone.queue.sync {
let tlkRecord = viewKeys.newTlk.fakeRecord(zoneID: rzid)
let classARecord = viewKeys.newClassA.fakeRecord(zoneID: rzid)
let classCRecord = viewKeys.newClassC.fakeRecord(zoneID: rzid)
let tlkPointerRecord = viewKeys.newTlk.fakeKeyPointer(zoneID: rzid)
let classAPointerRecord = viewKeys.newClassA.fakeKeyPointer(zoneID: rzid)
let classCPointerRecord = viewKeys.newClassC.fakeKeyPointer(zoneID: rzid)
// Some tests don't link everything needed to make zonekeys
// Those tests don't get this nice behavior
#if OCTAGON_TEST_FILL_ZONEKEYS
let zoneKeys = self.ckksZoneKeys[rzid] as? ZoneKeys ?? ZoneKeys(forZoneName: rzid.zoneName)
self.ckksZoneKeys[rzid] = zoneKeys
zoneKeys.tlk = CKKSKey(ckRecord: tlkRecord)
zoneKeys.classA = CKKSKey(ckRecord: classARecord)
zoneKeys.classC = CKKSKey(ckRecord: classCRecord)
zoneKeys.currentTLKPointer = CKKSCurrentKeyPointer(ckRecord: tlkPointerRecord)
zoneKeys.currentClassAPointer = CKKSCurrentKeyPointer(ckRecord: classAPointerRecord)
zoneKeys.currentClassCPointer = CKKSCurrentKeyPointer(ckRecord: classCPointerRecord)
#endif
let zoneRecords = [tlkRecord,
classARecord,
classCRecord,
tlkPointerRecord,
classAPointerRecord,
classCPointerRecord, ]
// TODO a rolled tlk too
zoneRecords.forEach { record in
fakeZone._onqueueAdd(toZone: record)
}
allRecords.append(contentsOf: zoneRecords)
}
} else {
// we made the zone above, shoudn't ever get here
print("Received an unexpected zone id: \(rzid)")
abort()
}
}
return allRecords
}
func store(tlkShares: [TLKShare]) -> [CKRecord] {
var allRecords: [CKRecord] = []
tlkShares.forEach { share in
let rzid = CKRecordZone.ID(zoneName: share.view)
var c = self.state.tlkShares[rzid] ?? []
c.append(share)
self.state.tlkShares[rzid] = c
if let fakeZone = self.fakeCKZones[rzid] as? FakeCKZone {
let record = share.fakeRecord(zoneID: rzid)
fakeZone.add(toZone: record)
allRecords.append(record)
} else {
print("Received an unexpected zone id: \(rzid)")
}
}
return allRecords
}
func establish(_ request: EstablishRequest, completion: @escaping (EstablishResponse?, Error?) -> Void) {
print("FakeCuttlefish: establish called")
if !self.state.peersByID.isEmpty {
completion(nil, FakeCuttlefishError.notEmpty)
}
// Before performing write, check if we should error
if let establishListener = self.establishListener {
let possibleError = establishListener(request)
guard possibleError == nil else {
completion(nil, possibleError)
return;
}
}
// Also check if we should bail due to conflicting viewKeys
if self.newKeysConflict(viewKeys: request.viewKeys) {
completion(nil, FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .keyHierarchyAlreadyExists))
return
}
self.state.peersByID[request.peer.peerID] = request.peer
self.state.bottles.append(request.bottle)
var keyRecords: [CKRecord] = []
keyRecords.append(contentsOf: store(viewKeys: request.viewKeys))
keyRecords.append(contentsOf: store(tlkShares: request.tlkShares))
self.makeSnapshot()
let response = EstablishResponse.with {
if self.nextEstablishReturnsMoreChanges {
$0.changes = Changes.with {
$0.more = true
}
self.nextEstablishReturnsMoreChanges = false
} else {
$0.changes = self.changesSince(snapshot: State())
}
$0.zoneKeyHierarchyRecords = keyRecords.map { try! CloudKitCode.Ckcode_RecordTransport($0) }
}
completion(response, nil)
self.pushNotify("establish")
}
func joinWithVoucher(_ request: JoinWithVoucherRequest, completion: @escaping (JoinWithVoucherResponse?, Error?) -> Void) {
print("FakeCuttlefish: joinWithVoucher called")
if let joinListener = self.joinListener {
let possibleError = joinListener(request)
guard possibleError == nil else {
completion(nil, possibleError)
return;
}
}
if let injectedError = self.nextJoinErrors.first {
print("FakeCuttlefish: erroring with injected error: ", String(describing: injectedError))
self.nextJoinErrors.removeFirst()
completion(nil, injectedError)
return
}
// Also check if we should bail due to conflicting viewKeys
if self.newKeysConflict(viewKeys: request.viewKeys) {
completion(nil, FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .keyHierarchyAlreadyExists))
return
}
guard let snapshot = self.snapshotsByChangeToken[request.changeToken] else {
completion(nil, FakeCuttlefishError.unknownChangeToken)
return
}
self.state.peersByID[request.peer.peerID] = request.peer
self.state.bottles.append(request.bottle)
var keyRecords: [CKRecord] = []
keyRecords.append(contentsOf: store(viewKeys: request.viewKeys))
keyRecords.append(contentsOf: store(tlkShares: request.tlkShares))
self.makeSnapshot()
completion(JoinWithVoucherResponse.with {
$0.changes = self.changesSince(snapshot: snapshot)
$0.zoneKeyHierarchyRecords = keyRecords.map { try! CloudKitCode.Ckcode_RecordTransport($0) }
}, nil)
self.pushNotify("joinWithVoucher")
}
func updateTrust(_ request: UpdateTrustRequest, completion: @escaping (UpdateTrustResponse?, Error?) -> Void) {
print("FakeCuttlefish: updateTrust called: changeToken: ", request.changeToken, "peerID: ", request.peerID)
if let injectedError = self.nextUpdateTrustErrors.first {
print("FakeCuttlefish: updateTrust erroring with injected error: ", String(describing: injectedError))
self.nextUpdateTrustErrors.removeFirst()
completion(nil, injectedError)
return
}
guard let snapshot = self.snapshotsByChangeToken[request.changeToken] else {
completion(nil, FakeCuttlefishError.unknownChangeToken)
return
}
guard var peer = self.state.peersByID[request.peerID] else {
completion(nil, FakeCuttlefishError.unknownPeerID)
return
}
if request.hasStableInfoAndSig {
peer.stableInfoAndSig = request.stableInfoAndSig
}
if request.hasDynamicInfoAndSig {
peer.dynamicInfoAndSig = request.dynamicInfoAndSig
}
self.state.peersByID[request.peerID] = peer
// Before performing write, check if we should error
if let updateListener = self.updateListener {
let possibleError = updateListener(request)
guard possibleError == nil else {
completion(nil, possibleError)
return;
}
}
// Also check if we should bail due to conflicting viewKeys
if self.newKeysConflict(viewKeys: request.viewKeys) {
completion(nil, FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .keyHierarchyAlreadyExists))
return
}
var keyRecords: [CKRecord] = []
keyRecords.append(contentsOf: store(viewKeys: request.viewKeys))
keyRecords.append(contentsOf: store(tlkShares: request.tlkShares))
let newDynamicInfo = TPPeerDynamicInfo(data: peer.dynamicInfoAndSig.peerDynamicInfo,
sig: peer.dynamicInfoAndSig.sig)
print("FakeCuttlefish: new peer dynamicInfo: ", request.peerID, String(describing: newDynamicInfo?.dictionaryRepresentation()))
self.makeSnapshot()
let response = UpdateTrustResponse.with {
$0.changes = self.changesSince(snapshot: snapshot)
$0.zoneKeyHierarchyRecords = keyRecords.map { try! CloudKitCode.Ckcode_RecordTransport($0) }
}
completion(response, nil)
self.pushNotify("updateTrust")
}
func setRecoveryKey(_ request: SetRecoveryKeyRequest, completion: @escaping (SetRecoveryKeyResponse?, Error?) -> Void) {
print("FakeCuttlefish: setRecoveryKey called")
guard let snapshot = self.snapshotsByChangeToken[request.changeToken] else {
completion(nil, FakeCuttlefishError.unknownChangeToken)
return
}
self.state.recoverySigningPubKey = request.recoverySigningPubKey
self.state.recoveryEncryptionPubKey = request.recoveryEncryptionPubKey
self.state.peersByID[request.peerID]?.stableInfoAndSig = request.stableInfoAndSig
self.makeSnapshot()
completion(SetRecoveryKeyResponse.with {
$0.changes = self.changesSince(snapshot: snapshot)
}, nil)
self.pushNotify("setRecoveryKey")
}
func fetchChanges(_ request: FetchChangesRequest, completion: @escaping (FetchChangesResponse?, Error?) -> Void) {
print("FakeCuttlefish: fetchChanges called: ", request.changeToken)
self.fetchChangesCalledCount += 1
if let fetchChangesListener = self.fetchChangesListener {
let possibleError = fetchChangesListener(request)
guard possibleError == nil else {
completion(nil, possibleError)
return;
}
}
if let injectedError = self.nextFetchErrors.first {
print("FakeCuttlefish: fetchChanges erroring with injected error: ", String(describing: injectedError))
self.nextFetchErrors.removeFirst()
completion(nil, injectedError)
return
}
let snapshot: State
if request.changeToken == "" {
snapshot = State()
} else {
guard let s = self.snapshotsByChangeToken[request.changeToken] else {
completion(nil, FakeCuttlefishError.unknownChangeToken)
return
}
snapshot = s
}
let response = FetchChangesResponse.with {
$0.changes = self.changesSince(snapshot: snapshot)
}
completion(response, nil)
}
func fetchViableBottles(_ request: FetchViableBottlesRequest, completion: @escaping (FetchViableBottlesResponse?, Error?) -> Void) {
print("FakeCuttlefish: fetchViableBottles called")
if let fetchViableBottlesListener = self.fetchViableBottlesListener {
let possibleError = fetchViableBottlesListener(request)
guard possibleError == nil else {
completion(nil, possibleError)
return;
}
}
if let injectedError = self.fetchViableBottlesError.first {
print("FakeCuttlefish: fetchViableBottles erroring with injected error: ", String(describing: injectedError))
self.fetchViableBottlesError.removeFirst()
completion(nil, injectedError)
return
}
let bottles = self.state.bottles.filter { $0.bottleID != fetchViableBottlesDontReturnBottleWithID }
completion(FetchViableBottlesResponse.with {
$0.viableBottles = bottles.compactMap { bottle in
EscrowPair.with {
$0.escrowRecordID = bottle.bottleID
$0.bottle = bottle
}
}
}, nil)
}
func fetchPolicyDocuments(_ request: FetchPolicyDocumentsRequest,
completion: @escaping (FetchPolicyDocumentsResponse?, Error?) -> Void) {
print("FakeCuttlefish: fetchPolicyDocuments called")
var response = FetchPolicyDocumentsResponse()
let policies = builtInPolicyDocuments()
let dummyPolicies = Dictionary(uniqueKeysWithValues: policies.map({ ($0.policyVersion, ($0.policyHash, $0.protobuf)) }))
for key in request.keys {
guard let (hash, data) = dummyPolicies[key.version] else {
continue
}
if hash == key.hash {
response.entries.append(PolicyDocumentMapEntry.with { $0.key = key; $0.value = data })
}
}
completion(response, nil)
}
func assertCuttlefishState(_ assertion: FakeCuttlefishAssertion) -> Bool {
return assertion.check(peer: self.state.peersByID[assertion.peer], target: self.state.peersByID[assertion.target])
}
func validatePeers(_: ValidatePeersRequest, completion: @escaping (ValidatePeersResponse?, Error?) -> Void) {
var response = ValidatePeersResponse()
response.validatorsHealth = 0.0
response.results = []
completion(response, nil)
}
func reportHealth(_: ReportHealthRequest, completion: @escaping (ReportHealthResponse?, Error?) -> Void) {
completion(ReportHealthResponse(), nil)
}
func pushHealthInquiry(_: HealthInquiryRequest, completion: @escaping (HealthInquiryResponse?, Error?) -> Void) {
completion(HealthInquiryResponse(), nil)
}
func getRepairAction(_ request: GetRepairActionRequest, completion: @escaping (GetRepairActionResponse?, Error?) -> Void) {
print("FakeCuttlefish: getRepairAction called")
if let healthListener = self.healthListener {
let possibleError = healthListener(request)
guard possibleError == nil else {
completion(nil, possibleError)
return;
}
}
if self.returnRepairEscrowResponse {
let response = GetRepairActionResponse.with {
$0.repairAction = .postRepairEscrow
}
completion(response, nil)
}
else if self.returnRepairAccountResponse {
let response = GetRepairActionResponse.with {
$0.repairAction = .postRepairAccount
}
completion(response, nil)
}
else if self.returnResetOctagonResponse {
let response = GetRepairActionResponse.with {
$0.repairAction = .resetOctagon
}
completion(response, nil)
}
else if self.returnNoActionResponse {
let response = GetRepairActionResponse.with {
$0.repairAction = .noAction
}
completion(response, nil)
} else if self.returnRepairErrorResponse != nil {
let response = GetRepairActionResponse.with {
$0.repairAction = .noAction
}
completion(response, self.returnRepairErrorResponse)
}
else {
completion(GetRepairActionResponse(), nil)
}
}
}
extension FakeCuttlefishServer : CloudKitCode.Invocable {
func invoke<RequestType, ResponseType>(function: String,
request: RequestType,
completion: @escaping (ResponseType?, Error?) -> Void) {
// Ideally we'd pattern match on both request and completion, but that crashes the swift compiler at this time (<rdar://problem/54412402>)
switch request {
case let request as ResetRequest:
self.reset(request, completion: completion as! (ResetResponse?, Error?) -> Void)
return
case let request as EstablishRequest:
self.establish(request, completion: completion as! (EstablishResponse?, Error?) -> Void)
return
case let request as JoinWithVoucherRequest:
self.joinWithVoucher(request, completion: completion as! (JoinWithVoucherResponse?, Error?) -> Void)
return
case let request as UpdateTrustRequest:
self.updateTrust(request, completion: completion as! (UpdateTrustResponse?, Error?) -> Void)
return
case let request as SetRecoveryKeyRequest:
self.setRecoveryKey(request, completion: completion as! (SetRecoveryKeyResponse?, Error?) -> Void)
return
case let request as FetchChangesRequest:
self.fetchChanges(request, completion: completion as! (FetchChangesResponse?, Error?) -> Void)
return
case let request as FetchViableBottlesRequest:
self.fetchViableBottles(request, completion: completion as! (FetchViableBottlesResponse?, Error?) -> Void)
return
case let request as FetchPolicyDocumentsRequest:
self.fetchPolicyDocuments(request, completion: completion as! (FetchPolicyDocumentsResponse?, Error?) -> Void)
return
case let request as ValidatePeersRequest:
self.validatePeers(request, completion: completion as! (ValidatePeersResponse?, Error?) -> Void)
return
case let request as ReportHealthRequest:
self.reportHealth(request, completion: completion as! (ReportHealthResponse?, Error?) -> Void)
return
case let request as HealthInquiryRequest:
self.pushHealthInquiry(request, completion: completion as! (HealthInquiryResponse?, Error?) -> Void)
return
case let request as GetRepairActionRequest:
self.getRepairAction(request, completion: completion as! (GetRepairActionResponse?, Error?) -> Void)
return
default:
abort()
}
}
}