2 // FakeCuttlefish.swift
5 // Created by Ben Williamson on 5/23/18.
11 enum FakeCuttlefishError: Error {
13 case unknownChangeToken
17 enum FakeCuttlefishOpinion {
19 case trustsByPreapproval
23 struct FakeCuttlefishAssertion: CustomStringConvertible {
25 let opinion: FakeCuttlefishOpinion
28 func check(peer: Peer?, target: Peer?) -> Bool {
29 guard let peer = peer else {
33 guard peer.hasDynamicInfoAndSig else {
34 // No opinions? You've failed this assertion.
38 let dynamicInfo = TPPeerDynamicInfo(data: peer.dynamicInfoAndSig.peerDynamicInfo, sig: peer.dynamicInfoAndSig.sig)
39 guard let realDynamicInfo = dynamicInfo else {
43 let targetPermanentInfo: TPPeerPermanentInfo? =
44 target != nil ? TPPeerPermanentInfo(peerID: self.target,
45 data: target!.permanentInfoAndSig.peerPermanentInfo,
46 sig: target!.permanentInfoAndSig.sig,
47 keyFactory: TPECPublicKeyFactory())
52 return realDynamicInfo.includedPeerIDs.contains(self.target)
53 case .trustsByPreapproval:
54 guard let pubSignSPKI = targetPermanentInfo?.signingPubKey.spki() else {
57 let hash = TPHashBuilder.hash(with: .SHA256, of: pubSignSPKI)
58 return realDynamicInfo.preapprovals.contains(hash)
60 return realDynamicInfo.excludedPeerIDs.contains(self.target)
64 var description: String {
65 return "DCA:(\(self.peer)\(self.opinion)\(self.target))"
69 @objc class FakeCuttlefishNotify: NSObject {
70 let pushes: (Data) -> Void
71 let containerName: String
72 @objc init(_ containerName: String, pushes: @escaping (Data) -> Void) {
73 self.containerName = containerName
77 @objc public func notify(_ function: String) throws {
78 let notification: [String: Dictionary<String, Any>] = [
79 "aps": ["content-available": 1],
82 "c": self.containerName,
87 payload = try JSONSerialization.data(withJSONObject: notification)
96 func fakeRecord(zoneID: CKRecordZone.ID) -> CKRecord {
97 let recordID = CKRecord.ID(__recordName: self.uuid, zoneID: zoneID)
98 let record = CKRecord(recordType: SecCKRecordIntermediateKeyType, recordID: recordID)
100 record[SecCKRecordWrappedKeyKey] = self.wrappedkeyBase64
102 switch(self.keyclass) {
104 record[SecCKRecordKeyClassKey] = "tlk"
106 record[SecCKRecordKeyClassKey] = "classA"
108 record[SecCKRecordKeyClassKey] = "classC"
113 if self.parentkeyUuid.count > 0 {
114 // TODO: no idea how to tell it about the 'verify' action
115 record[SecCKRecordParentKeyRefKey] = CKRecord.Reference(recordID: CKRecord.ID(__recordName: self.parentkeyUuid, zoneID: zoneID), action: .none)
121 func fakeKeyPointer(zoneID: CKRecordZone.ID) -> CKRecord {
122 let recordName: String
123 switch(self.keyclass) {
127 recordName = "classA"
129 recordName = "classC"
134 let recordID = CKRecord.ID(__recordName: recordName, zoneID: zoneID)
135 let record = CKRecord(recordType: SecCKRecordCurrentKeyType, recordID: recordID)
137 // TODO: no idea how to tell it about the 'verify' action
138 record[SecCKRecordParentKeyRefKey] = CKRecord.Reference(recordID: CKRecord.ID(__recordName: self.uuid, zoneID: zoneID), action: .none)
145 func fakeRecord(zoneID: CKRecordZone.ID) -> CKRecord {
146 let recordID = CKRecord.ID(__recordName: "tlkshare-\(self.keyUuid)::\(self.receiver)::\(self.sender)", zoneID: zoneID)
147 let record = CKRecord(recordType: SecCKRecordTLKShareType, recordID: recordID)
149 record[SecCKRecordSenderPeerID] = self.sender
150 record[SecCKRecordReceiverPeerID] = self.receiver
151 record[SecCKRecordReceiverPublicEncryptionKey] = self.receiverPublicEncryptionKey
152 record[SecCKRecordCurve] = self.curve
153 record[SecCKRecordVersion] = self.version
154 record[SecCKRecordEpoch] = self.epoch
155 record[SecCKRecordPoisoned] = self.poisoned
157 // TODO: no idea how to tell it about the 'verify' action
158 record[SecCKRecordParentKeyRefKey] = CKRecord.Reference(recordID: CKRecord.ID(__recordName: self.keyUuid, zoneID: zoneID), action: .none)
160 record[SecCKRecordWrappedKeyKey] = self.wrappedkey
161 record[SecCKRecordSignature] = self.signature
167 class FakeCuttlefishServer: CuttlefishAPIAsync {
170 var peersByID: [String: Peer] = [:]
171 var recoverySigningPubKey: Data?
172 var recoveryEncryptionPubKey: Data?
173 var bottles: [Bottle] = []
175 var viewKeys: [CKRecordZone.ID: ViewKeys] = [:]
176 var tlkShares: [CKRecordZone.ID: [TLKShare]] = [:]
183 var snapshotsByChangeToken: [String: State] = [:]
184 var currentChange: Int = 0
185 var currentChangeToken: String = ""
186 let notify: FakeCuttlefishNotify?
188 //var fakeCKZones: [CKRecordZone.ID: FakeCKZone]
189 var fakeCKZones: NSMutableDictionary
191 // @property (nullable) NSMutableDictionary<CKRecordZoneID*, ZoneKeys*>* keys;
192 var ckksZoneKeys: NSMutableDictionary
194 var nextFetchErrors: [Error] = []
195 var fetchViableBottlesError: [Error] = []
196 var nextJoinErrors: [Error] = []
197 var nextUpdateTrustErrors: [Error] = []
198 var returnNoActionResponse: Bool = false
199 var returnRepairAccountResponse: Bool = false
200 var returnRepairEscrowResponse: Bool = false
201 var returnResetOctagonResponse: Bool = false
202 var returnRepairErrorResponse: Error?
203 var fetchChangesCalledCount: Int = 0
205 var nextEstablishReturnsMoreChanges: Bool = false
207 var establishListener: ((EstablishRequest) -> NSError?)?
208 var updateListener: ((UpdateTrustRequest) -> NSError?)?
209 var fetchChangesListener: ((FetchChangesRequest) -> NSError?)?
210 var joinListener: ((JoinWithVoucherRequest) -> NSError?)?
211 var healthListener: ((GetRepairActionRequest) -> NSError?)?
212 var fetchViableBottlesListener: ((FetchViableBottlesRequest) -> NSError?)?
214 var fetchViableBottlesDontReturnBottleWithID: String?
216 init(_ notify: FakeCuttlefishNotify?, ckZones: NSMutableDictionary, ckksZoneKeys: NSMutableDictionary) {
218 self.fakeCKZones = ckZones
219 self.ckksZoneKeys = ckksZoneKeys
222 func deleteAllPeers() {
223 self.state.peersByID.removeAll()
227 func pushNotify(_ function: String) {
228 if let notify = self.notify {
230 try notify.notify(function)
236 static func makeCloudKitCuttlefishError(code: CuttlefishErrorCode) -> NSError {
237 return CKPrettyError(domain: CKInternalErrorDomain,
238 code: CKInternalErrorCode.errorInternalPluginError.rawValue,
239 userInfo: [NSUnderlyingErrorKey: NSError(domain: CuttlefishErrorDomain,
244 func makeSnapshot() {
245 self.currentChange += 1
246 self.currentChangeToken = "change\(self.currentChange)"
247 self.snapshotsByChangeToken[self.currentChangeToken] = self.state
250 func changesSince(snapshot: State) -> Changes {
251 return Changes.with { changes in
252 changes.changeToken = self.currentChangeToken
254 changes.differences = self.state.peersByID.compactMap({ (key: String, value: Peer) -> PeerDifference? in
255 let old = snapshot.peersByID[key]
257 return PeerDifference.with {
260 } else if old != value {
261 return PeerDifference.with {
268 snapshot.peersByID.forEach { (key: String, _: Peer) in
269 if nil == self.state.peersByID[key] {
270 changes.differences.append(PeerDifference.with {
271 $0.remove = Peer.with {
278 if self.state.recoverySigningPubKey != snapshot.recoverySigningPubKey {
279 changes.recoverySigningPubKey = self.state.recoverySigningPubKey ?? Data()
281 if self.state.recoveryEncryptionPubKey != snapshot.recoveryEncryptionPubKey {
282 changes.recoveryEncryptionPubKey = self.state.recoveryEncryptionPubKey ?? Data()
288 func reset(_ request: ResetRequest, completion: @escaping (ResetResponse?, Error?) -> Void) {
289 print("FakeCuttlefish: reset called")
292 completion(ResetResponse.with {
293 $0.changes = self.changesSince(snapshot: State())
295 self.pushNotify("reset")
298 func newKeysConflict(viewKeys: [ViewKeys]) -> Bool {
299 #if OCTAGON_TEST_FILL_ZONEKEYS
300 for keys in viewKeys {
301 let rzid = CKRecordZone.ID(zoneName: keys.view)
303 if let currentViewKeys = self.ckksZoneKeys[rzid] as? CKKSCurrentKeySet {
304 // Uploading the current view keys is okay. Fail only if they don't match
305 if keys.newTlk.uuid != currentViewKeys.tlk!.uuid ||
306 keys.newClassA.uuid != currentViewKeys.classA!.uuid ||
307 keys.newClassC.uuid != currentViewKeys.classC!.uuid {
317 func store(viewKeys: [ViewKeys]) -> [CKRecord] {
318 var allRecords: [CKRecord] = []
320 viewKeys.forEach { viewKeys in
321 let rzid = CKRecordZone.ID(zoneName: viewKeys.view)
322 self.state.viewKeys[rzid] = viewKeys
324 // Real cuttlefish makes these zones for you
325 if self.fakeCKZones[rzid] == nil {
326 self.fakeCKZones[rzid] = FakeCKZone(zone: rzid)
329 if let fakeZone = self.fakeCKZones[rzid] as? FakeCKZone {
330 fakeZone.queue.sync {
331 let tlkRecord = viewKeys.newTlk.fakeRecord(zoneID: rzid)
332 let classARecord = viewKeys.newClassA.fakeRecord(zoneID: rzid)
333 let classCRecord = viewKeys.newClassC.fakeRecord(zoneID: rzid)
335 let tlkPointerRecord = viewKeys.newTlk.fakeKeyPointer(zoneID: rzid)
336 let classAPointerRecord = viewKeys.newClassA.fakeKeyPointer(zoneID: rzid)
337 let classCPointerRecord = viewKeys.newClassC.fakeKeyPointer(zoneID: rzid)
339 // Some tests don't link everything needed to make zonekeys
340 // Those tests don't get this nice behavior
341 #if OCTAGON_TEST_FILL_ZONEKEYS
342 let zoneKeys = self.ckksZoneKeys[rzid] as? ZoneKeys ?? ZoneKeys(forZoneName: rzid.zoneName)
343 self.ckksZoneKeys[rzid] = zoneKeys
345 zoneKeys.tlk = CKKSKey(ckRecord: tlkRecord)
346 zoneKeys.classA = CKKSKey(ckRecord: classARecord)
347 zoneKeys.classC = CKKSKey(ckRecord: classCRecord)
349 zoneKeys.currentTLKPointer = CKKSCurrentKeyPointer(ckRecord: tlkPointerRecord)
350 zoneKeys.currentClassAPointer = CKKSCurrentKeyPointer(ckRecord: classAPointerRecord)
351 zoneKeys.currentClassCPointer = CKKSCurrentKeyPointer(ckRecord: classCPointerRecord)
354 let zoneRecords = [tlkRecord,
359 classCPointerRecord, ]
360 // TODO a rolled tlk too
362 zoneRecords.forEach { record in
363 fakeZone._onqueueAdd(toZone: record)
365 allRecords.append(contentsOf: zoneRecords)
368 // we made the zone above, shoudn't ever get here
369 print("Received an unexpected zone id: \(rzid)")
376 func store(tlkShares: [TLKShare]) -> [CKRecord] {
377 var allRecords: [CKRecord] = []
379 tlkShares.forEach { share in
380 let rzid = CKRecordZone.ID(zoneName: share.view)
382 var c = self.state.tlkShares[rzid] ?? []
384 self.state.tlkShares[rzid] = c
386 if let fakeZone = self.fakeCKZones[rzid] as? FakeCKZone {
387 let record = share.fakeRecord(zoneID: rzid)
388 fakeZone.add(toZone: record)
389 allRecords.append(record)
392 print("Received an unexpected zone id: \(rzid)")
399 func establish(_ request: EstablishRequest, completion: @escaping (EstablishResponse?, Error?) -> Void) {
400 print("FakeCuttlefish: establish called")
401 if !self.state.peersByID.isEmpty {
402 completion(nil, FakeCuttlefishError.notEmpty)
405 // Before performing write, check if we should error
406 if let establishListener = self.establishListener {
407 let possibleError = establishListener(request)
408 guard possibleError == nil else {
409 completion(nil, possibleError)
414 // Also check if we should bail due to conflicting viewKeys
415 if self.newKeysConflict(viewKeys: request.viewKeys) {
416 completion(nil, FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .keyHierarchyAlreadyExists))
420 self.state.peersByID[request.peer.peerID] = request.peer
421 self.state.bottles.append(request.bottle)
423 var keyRecords: [CKRecord] = []
424 keyRecords.append(contentsOf: store(viewKeys: request.viewKeys))
425 keyRecords.append(contentsOf: store(tlkShares: request.tlkShares))
430 let response = EstablishResponse.with {
431 if self.nextEstablishReturnsMoreChanges {
432 $0.changes = Changes.with {
435 self.nextEstablishReturnsMoreChanges = false
437 $0.changes = self.changesSince(snapshot: State())
439 $0.zoneKeyHierarchyRecords = keyRecords.map { try! CloudKitCode.Ckcode_RecordTransport($0) }
442 completion(response, nil)
443 self.pushNotify("establish")
446 func joinWithVoucher(_ request: JoinWithVoucherRequest, completion: @escaping (JoinWithVoucherResponse?, Error?) -> Void) {
447 print("FakeCuttlefish: joinWithVoucher called")
449 if let joinListener = self.joinListener {
450 let possibleError = joinListener(request)
451 guard possibleError == nil else {
452 completion(nil, possibleError)
457 if let injectedError = self.nextJoinErrors.first {
458 print("FakeCuttlefish: erroring with injected error: ", String(describing: injectedError))
459 self.nextJoinErrors.removeFirst()
460 completion(nil, injectedError)
464 // Also check if we should bail due to conflicting viewKeys
465 if self.newKeysConflict(viewKeys: request.viewKeys) {
466 completion(nil, FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .keyHierarchyAlreadyExists))
470 guard let snapshot = self.snapshotsByChangeToken[request.changeToken] else {
471 completion(nil, FakeCuttlefishError.unknownChangeToken)
474 self.state.peersByID[request.peer.peerID] = request.peer
475 self.state.bottles.append(request.bottle)
477 var keyRecords: [CKRecord] = []
478 keyRecords.append(contentsOf: store(viewKeys: request.viewKeys))
479 keyRecords.append(contentsOf: store(tlkShares: request.tlkShares))
483 completion(JoinWithVoucherResponse.with {
484 $0.changes = self.changesSince(snapshot: snapshot)
485 $0.zoneKeyHierarchyRecords = keyRecords.map { try! CloudKitCode.Ckcode_RecordTransport($0) }
487 self.pushNotify("joinWithVoucher")
490 func updateTrust(_ request: UpdateTrustRequest, completion: @escaping (UpdateTrustResponse?, Error?) -> Void) {
491 print("FakeCuttlefish: updateTrust called: changeToken: ", request.changeToken, "peerID: ", request.peerID)
493 if let injectedError = self.nextUpdateTrustErrors.first {
494 print("FakeCuttlefish: updateTrust erroring with injected error: ", String(describing: injectedError))
495 self.nextUpdateTrustErrors.removeFirst()
496 completion(nil, injectedError)
500 guard let snapshot = self.snapshotsByChangeToken[request.changeToken] else {
501 completion(nil, FakeCuttlefishError.unknownChangeToken)
504 guard var peer = self.state.peersByID[request.peerID] else {
505 completion(nil, FakeCuttlefishError.unknownPeerID)
508 if request.hasStableInfoAndSig {
509 peer.stableInfoAndSig = request.stableInfoAndSig
511 if request.hasDynamicInfoAndSig {
512 peer.dynamicInfoAndSig = request.dynamicInfoAndSig
514 self.state.peersByID[request.peerID] = peer
516 // Before performing write, check if we should error
517 if let updateListener = self.updateListener {
518 let possibleError = updateListener(request)
519 guard possibleError == nil else {
520 completion(nil, possibleError)
525 // Also check if we should bail due to conflicting viewKeys
526 if self.newKeysConflict(viewKeys: request.viewKeys) {
527 completion(nil, FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .keyHierarchyAlreadyExists))
531 var keyRecords: [CKRecord] = []
532 keyRecords.append(contentsOf: store(viewKeys: request.viewKeys))
533 keyRecords.append(contentsOf: store(tlkShares: request.tlkShares))
535 let newDynamicInfo = TPPeerDynamicInfo(data: peer.dynamicInfoAndSig.peerDynamicInfo,
536 sig: peer.dynamicInfoAndSig.sig)
537 print("FakeCuttlefish: new peer dynamicInfo: ", request.peerID, String(describing: newDynamicInfo?.dictionaryRepresentation()))
540 let response = UpdateTrustResponse.with {
541 $0.changes = self.changesSince(snapshot: snapshot)
542 $0.zoneKeyHierarchyRecords = keyRecords.map { try! CloudKitCode.Ckcode_RecordTransport($0) }
545 completion(response, nil)
546 self.pushNotify("updateTrust")
549 func setRecoveryKey(_ request: SetRecoveryKeyRequest, completion: @escaping (SetRecoveryKeyResponse?, Error?) -> Void) {
550 print("FakeCuttlefish: setRecoveryKey called")
551 guard let snapshot = self.snapshotsByChangeToken[request.changeToken] else {
552 completion(nil, FakeCuttlefishError.unknownChangeToken)
555 self.state.recoverySigningPubKey = request.recoverySigningPubKey
556 self.state.recoveryEncryptionPubKey = request.recoveryEncryptionPubKey
557 self.state.peersByID[request.peerID]?.stableInfoAndSig = request.stableInfoAndSig
559 completion(SetRecoveryKeyResponse.with {
560 $0.changes = self.changesSince(snapshot: snapshot)
562 self.pushNotify("setRecoveryKey")
565 func fetchChanges(_ request: FetchChangesRequest, completion: @escaping (FetchChangesResponse?, Error?) -> Void) {
566 print("FakeCuttlefish: fetchChanges called: ", request.changeToken)
568 self.fetchChangesCalledCount += 1
570 if let fetchChangesListener = self.fetchChangesListener {
571 let possibleError = fetchChangesListener(request)
572 guard possibleError == nil else {
573 completion(nil, possibleError)
578 if let injectedError = self.nextFetchErrors.first {
579 print("FakeCuttlefish: fetchChanges erroring with injected error: ", String(describing: injectedError))
580 self.nextFetchErrors.removeFirst()
581 completion(nil, injectedError)
586 if request.changeToken == "" {
589 guard let s = self.snapshotsByChangeToken[request.changeToken] else {
590 completion(nil, FakeCuttlefishError.unknownChangeToken)
595 let response = FetchChangesResponse.with {
596 $0.changes = self.changesSince(snapshot: snapshot)
599 completion(response, nil)
602 func fetchViableBottles(_ request: FetchViableBottlesRequest, completion: @escaping (FetchViableBottlesResponse?, Error?) -> Void) {
603 print("FakeCuttlefish: fetchViableBottles called")
605 if let fetchViableBottlesListener = self.fetchViableBottlesListener {
606 let possibleError = fetchViableBottlesListener(request)
607 guard possibleError == nil else {
608 completion(nil, possibleError)
613 if let injectedError = self.fetchViableBottlesError.first {
614 print("FakeCuttlefish: fetchViableBottles erroring with injected error: ", String(describing: injectedError))
615 self.fetchViableBottlesError.removeFirst()
616 completion(nil, injectedError)
620 let bottles = self.state.bottles.filter { $0.bottleID != fetchViableBottlesDontReturnBottleWithID }
621 completion(FetchViableBottlesResponse.with {
622 $0.viableBottles = bottles.compactMap { bottle in
624 $0.escrowRecordID = bottle.bottleID
631 func fetchPolicyDocuments(_ request: FetchPolicyDocumentsRequest,
632 completion: @escaping (FetchPolicyDocumentsResponse?, Error?) -> Void) {
633 print("FakeCuttlefish: fetchPolicyDocuments called")
634 var response = FetchPolicyDocumentsResponse()
636 let policies = builtInPolicyDocuments()
637 let dummyPolicies = Dictionary(uniqueKeysWithValues: policies.map({ ($0.policyVersion, ($0.policyHash, $0.protobuf)) }))
638 for key in request.keys {
639 guard let (hash, data) = dummyPolicies[key.version] else {
642 if hash == key.hash {
643 response.entries.append(PolicyDocumentMapEntry.with { $0.key = key; $0.value = data })
646 completion(response, nil)
649 func assertCuttlefishState(_ assertion: FakeCuttlefishAssertion) -> Bool {
650 return assertion.check(peer: self.state.peersByID[assertion.peer], target: self.state.peersByID[assertion.target])
653 func validatePeers(_: ValidatePeersRequest, completion: @escaping (ValidatePeersResponse?, Error?) -> Void) {
654 var response = ValidatePeersResponse()
655 response.validatorsHealth = 0.0
656 response.results = []
657 completion(response, nil)
659 func reportHealth(_: ReportHealthRequest, completion: @escaping (ReportHealthResponse?, Error?) -> Void) {
660 completion(ReportHealthResponse(), nil)
662 func pushHealthInquiry(_: HealthInquiryRequest, completion: @escaping (HealthInquiryResponse?, Error?) -> Void) {
663 completion(HealthInquiryResponse(), nil)
666 func getRepairAction(_ request: GetRepairActionRequest, completion: @escaping (GetRepairActionResponse?, Error?) -> Void) {
667 print("FakeCuttlefish: getRepairAction called")
669 if let healthListener = self.healthListener {
670 let possibleError = healthListener(request)
671 guard possibleError == nil else {
672 completion(nil, possibleError)
677 if self.returnRepairEscrowResponse {
678 let response = GetRepairActionResponse.with {
679 $0.repairAction = .postRepairEscrow
681 completion(response, nil)
683 else if self.returnRepairAccountResponse {
684 let response = GetRepairActionResponse.with {
685 $0.repairAction = .postRepairAccount
687 completion(response, nil)
689 else if self.returnResetOctagonResponse {
690 let response = GetRepairActionResponse.with {
691 $0.repairAction = .resetOctagon
693 completion(response, nil)
695 else if self.returnNoActionResponse {
696 let response = GetRepairActionResponse.with {
697 $0.repairAction = .noAction
699 completion(response, nil)
700 } else if self.returnRepairErrorResponse != nil {
701 let response = GetRepairActionResponse.with {
702 $0.repairAction = .noAction
704 completion(response, self.returnRepairErrorResponse)
707 completion(GetRepairActionResponse(), nil)
712 extension FakeCuttlefishServer : CloudKitCode.Invocable {
713 func invoke<RequestType, ResponseType>(function: String,
714 request: RequestType,
715 completion: @escaping (ResponseType?, Error?) -> Void) {
716 // Ideally we'd pattern match on both request and completion, but that crashes the swift compiler at this time (<rdar://problem/54412402>)
718 case let request as ResetRequest:
719 self.reset(request, completion: completion as! (ResetResponse?, Error?) -> Void)
721 case let request as EstablishRequest:
722 self.establish(request, completion: completion as! (EstablishResponse?, Error?) -> Void)
724 case let request as JoinWithVoucherRequest:
725 self.joinWithVoucher(request, completion: completion as! (JoinWithVoucherResponse?, Error?) -> Void)
727 case let request as UpdateTrustRequest:
728 self.updateTrust(request, completion: completion as! (UpdateTrustResponse?, Error?) -> Void)
730 case let request as SetRecoveryKeyRequest:
731 self.setRecoveryKey(request, completion: completion as! (SetRecoveryKeyResponse?, Error?) -> Void)
733 case let request as FetchChangesRequest:
734 self.fetchChanges(request, completion: completion as! (FetchChangesResponse?, Error?) -> Void)
736 case let request as FetchViableBottlesRequest:
737 self.fetchViableBottles(request, completion: completion as! (FetchViableBottlesResponse?, Error?) -> Void)
739 case let request as FetchPolicyDocumentsRequest:
740 self.fetchPolicyDocuments(request, completion: completion as! (FetchPolicyDocumentsResponse?, Error?) -> Void)
742 case let request as ValidatePeersRequest:
743 self.validatePeers(request, completion: completion as! (ValidatePeersResponse?, Error?) -> Void)
745 case let request as ReportHealthRequest:
746 self.reportHealth(request, completion: completion as! (ReportHealthResponse?, Error?) -> Void)
748 case let request as HealthInquiryRequest:
749 self.pushHealthInquiry(request, completion: completion as! (HealthInquiryResponse?, Error?) -> Void)
751 case let request as GetRepairActionRequest:
752 self.getRepairAction(request, completion: completion as! (GetRepairActionResponse?, Error?) -> Void)