Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0eff90a904 | ||
|
|
431bd74c29 | ||
|
|
6acaf5bf83 | ||
|
|
a35ac627a9 | ||
|
|
b4dd03d96b | ||
|
|
c6b7a04694 |
@ -8,6 +8,9 @@ GIT
|
||||
fourflusher (~> 2.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
|
||||
GEM
|
||||
specs:
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
@ -290,4 +293,4 @@ DEPENDENCIES
|
||||
fastlane!
|
||||
|
||||
BUNDLED WITH
|
||||
2.1.4
|
||||
2.2.24
|
||||
|
||||
@ -26,8 +26,8 @@ target 'Example' do
|
||||
# pod 'MobileCoin', git: 'https://github.com/mobilecoinofficial/MobileCoin-Swift.git'
|
||||
# pod 'MobileCoin/Core', git: 'https://github.com/mobilecoinofficial/MobileCoin-Swift.git', testspecs: ['Tests', 'IntegrationTests']
|
||||
|
||||
pod 'LibMobileCoin', binary: true
|
||||
# pod 'LibMobileCoin', path: '../Vendor/libmobilecoin-ios-artifacts'
|
||||
# pod 'LibMobileCoin', binary: true
|
||||
pod 'LibMobileCoin', path: '../Vendor/libmobilecoin-ios-artifacts'
|
||||
# pod 'LibMobileCoin', podspec: '../Vendor/libmobilecoin-ios-artifacts/LibMobileCoin.podspec'
|
||||
# pod 'LibMobileCoin', git: 'https://github.com/mobilecoinofficial/libmobilecoin-ios-artifacts.git'
|
||||
|
||||
|
||||
@ -104,7 +104,7 @@ PODS:
|
||||
DEPENDENCIES:
|
||||
- gRPC-Swift
|
||||
- Keys (from `Pods/CocoaPodsKeys`)
|
||||
- LibMobileCoin
|
||||
- LibMobileCoin (from `../Vendor/libmobilecoin-ios-artifacts`)
|
||||
- MobileCoin (from `..`)
|
||||
- MobileCoin/Core (from `..`)
|
||||
- MobileCoin/Core/IntegrationTests (from `..`)
|
||||
@ -124,7 +124,6 @@ SPEC REPOS:
|
||||
- CNIOLinux
|
||||
- CNIOWindows
|
||||
- gRPC-Swift
|
||||
- LibMobileCoin
|
||||
- Logging
|
||||
- SwiftLint
|
||||
- SwiftNIO
|
||||
@ -142,6 +141,8 @@ SPEC REPOS:
|
||||
EXTERNAL SOURCES:
|
||||
Keys:
|
||||
:path: Pods/CocoaPodsKeys
|
||||
LibMobileCoin:
|
||||
:path: "../Vendor/libmobilecoin-ios-artifacts"
|
||||
MobileCoin:
|
||||
:path: ".."
|
||||
|
||||
@ -172,6 +173,6 @@ SPEC CHECKSUMS:
|
||||
SwiftNIOTransportServices: 896c9a4ac98698d32aa2feea7657ade219ae80bb
|
||||
SwiftProtobuf: 3320217e9d8fb75f36b40282e78c482640fd75dd
|
||||
|
||||
PODFILE CHECKSUM: 004d4412cc1559f32bc126f553599fcfcaac859f
|
||||
PODFILE CHECKSUM: a0d081de36bb0e26c2f240e65e5e63f9f111ec19
|
||||
|
||||
COCOAPODS: 1.9.3
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
GEM
|
||||
specs:
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
@ -107,4 +110,4 @@ DEPENDENCIES
|
||||
jazzy!
|
||||
|
||||
BUNDLED WITH
|
||||
2.1.4
|
||||
2.2.24
|
||||
|
||||
@ -22,11 +22,11 @@ final class FogReportManager {
|
||||
completion: @escaping (Result<Report_ReportResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
logger.info("reportUrl: \(reportUrl.url)")
|
||||
let reportService = serviceProvider.fogReportService(for: reportUrl)
|
||||
|
||||
self.inner.accessAsync {
|
||||
let reportServer = $0.reportServer(for: reportUrl)
|
||||
reportServer.reports(reportService: reportService, completion: completion)
|
||||
serviceProvider.fogReportService(for: reportUrl) { reportService in
|
||||
self.inner.accessAsync {
|
||||
let reportServer = $0.reportServer(for: reportUrl)
|
||||
reportServer.reports(reportService: reportService, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,14 +36,14 @@ final class FogReportManager {
|
||||
completion: @escaping (Result<Report_ReportResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
logger.info("reportUrl: \(reportUrl.url), reportParams: \(reportParams)")
|
||||
let reportService = serviceProvider.fogReportService(for: reportUrl)
|
||||
|
||||
self.inner.accessAsync {
|
||||
let reportServer = $0.reportServer(for: reportUrl)
|
||||
reportServer.reports(
|
||||
reportService: reportService,
|
||||
reportParams: reportParams,
|
||||
completion: completion)
|
||||
serviceProvider.fogReportService(for: reportUrl) { reportService in
|
||||
self.inner.accessAsync {
|
||||
let reportServer = $0.reportServer(for: reportUrl)
|
||||
reportServer.reports(
|
||||
reportService: reportService,
|
||||
reportParams: reportParams,
|
||||
completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,9 +19,8 @@ final class FogResolverManager {
|
||||
) {
|
||||
self.serialQueue = DispatchQueue(label: "com.mobilecoin.\(Self.self)", target: targetQueue)
|
||||
self.reportAttestation = fogReportAttestation
|
||||
self.reportManager = FogReportManager(
|
||||
serviceProvider: serviceProvider,
|
||||
targetQueue: targetQueue)
|
||||
self.reportManager =
|
||||
FogReportManager(serviceProvider: serviceProvider, targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func fogResolver(
|
||||
|
||||
@ -22,7 +22,6 @@ public final class MobileCoinClient {
|
||||
}
|
||||
|
||||
private let accountLock: ReadWriteDispatchLock<Account>
|
||||
private let inner: SerialDispatchLock<Inner>
|
||||
private let serialQueue: DispatchQueue
|
||||
private let callbackQueue: DispatchQueue
|
||||
|
||||
@ -30,6 +29,8 @@ public final class MobileCoinClient {
|
||||
private let mixinSelectionStrategy: MixinSelectionStrategy
|
||||
private let fogQueryScalingStrategy: FogQueryScalingStrategy
|
||||
|
||||
private let serviceProvider: ServiceProvider
|
||||
private let fogResolverManager: FogResolverManager
|
||||
private let feeFetcher: BlockchainFeeFetcher
|
||||
|
||||
init(accountKey: AccountKeyWithFog, config: Config) {
|
||||
@ -45,16 +46,12 @@ public final class MobileCoinClient {
|
||||
self.mixinSelectionStrategy = config.mixinSelectionStrategy
|
||||
self.fogQueryScalingStrategy = config.fogQueryScalingStrategy
|
||||
|
||||
let serviceProvider =
|
||||
self.serviceProvider =
|
||||
DefaultServiceProvider(networkConfig: config.networkConfig, targetQueue: serialQueue)
|
||||
let fogResolverManager = FogResolverManager(
|
||||
self.fogResolverManager = FogResolverManager(
|
||||
fogReportAttestation: config.networkConfig.fogReportAttestation,
|
||||
serviceProvider: serviceProvider,
|
||||
targetQueue: serialQueue)
|
||||
|
||||
let inner = Inner(serviceProvider: serviceProvider, fogResolverManager: fogResolverManager)
|
||||
self.inner = .init(inner, targetQueue: serialQueue)
|
||||
|
||||
self.feeFetcher = BlockchainFeeFetcher(
|
||||
blockchainService: serviceProvider.blockchainService,
|
||||
minimumFeeCacheTTL: config.minimumFeeCacheTTL,
|
||||
@ -69,29 +66,31 @@ public final class MobileCoinClient {
|
||||
accountLock.readSync { $0.cachedAccountActivity }
|
||||
}
|
||||
|
||||
public func setTransportProtocol(_ transportProtocol: TransportProtocol) {
|
||||
serviceProvider.setTransportProtocolOption(transportProtocol.option)
|
||||
}
|
||||
|
||||
public func setConsensusBasicAuthorization(username: String, password: String) {
|
||||
let credentials = BasicCredentials(username: username, password: password)
|
||||
inner.accessAsync { $0.serviceProvider.setConsensusAuthorization(credentials: credentials) }
|
||||
serviceProvider.setConsensusAuthorization(credentials: credentials)
|
||||
}
|
||||
|
||||
public func setFogBasicAuthorization(username: String, password: String) {
|
||||
let credentials = BasicCredentials(username: username, password: password)
|
||||
inner.accessAsync { $0.serviceProvider.setFogUserAuthorization(credentials: credentials) }
|
||||
serviceProvider.setFogUserAuthorization(credentials: credentials)
|
||||
}
|
||||
|
||||
public func updateBalance(completion: @escaping (Result<Balance, ConnectionError>) -> Void) {
|
||||
inner.accessAsync {
|
||||
Account.BalanceUpdater(
|
||||
account: self.accountLock,
|
||||
fogViewService: $0.serviceProvider.fogViewService,
|
||||
fogKeyImageService: $0.serviceProvider.fogKeyImageService,
|
||||
fogBlockService: $0.serviceProvider.fogBlockService,
|
||||
fogQueryScalingStrategy: self.fogQueryScalingStrategy,
|
||||
targetQueue: self.serialQueue
|
||||
).updateBalance { result in
|
||||
self.callbackQueue.async {
|
||||
completion(result)
|
||||
}
|
||||
Account.BalanceUpdater(
|
||||
account: accountLock,
|
||||
fogViewService: serviceProvider.fogViewService,
|
||||
fogKeyImageService: serviceProvider.fogKeyImageService,
|
||||
fogBlockService: serviceProvider.fogBlockService,
|
||||
fogQueryScalingStrategy: fogQueryScalingStrategy,
|
||||
targetQueue: serialQueue
|
||||
).updateBalance { result in
|
||||
self.callbackQueue.async {
|
||||
completion(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -142,19 +141,17 @@ public final class MobileCoinClient {
|
||||
Result<(transaction: Transaction, receipt: Receipt), TransactionPreparationError>
|
||||
) -> Void
|
||||
) {
|
||||
inner.accessAsync {
|
||||
Account.TransactionOperations(
|
||||
account: self.accountLock,
|
||||
fogMerkleProofService: $0.serviceProvider.fogMerkleProofService,
|
||||
fogResolverManager: $0.fogResolverManager,
|
||||
feeFetcher: self.feeFetcher,
|
||||
txOutSelectionStrategy: self.txOutSelectionStrategy,
|
||||
mixinSelectionStrategy: self.mixinSelectionStrategy,
|
||||
targetQueue: self.serialQueue
|
||||
).prepareTransaction(to: recipient, amount: amount, fee: fee) { result in
|
||||
self.callbackQueue.async {
|
||||
completion(result)
|
||||
}
|
||||
Account.TransactionOperations(
|
||||
account: accountLock,
|
||||
fogMerkleProofService: serviceProvider.fogMerkleProofService,
|
||||
fogResolverManager: fogResolverManager,
|
||||
feeFetcher: feeFetcher,
|
||||
txOutSelectionStrategy: txOutSelectionStrategy,
|
||||
mixinSelectionStrategy: mixinSelectionStrategy,
|
||||
targetQueue: serialQueue
|
||||
).prepareTransaction(to: recipient, amount: amount, fee: fee) { result in
|
||||
self.callbackQueue.async {
|
||||
completion(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -167,19 +164,17 @@ public final class MobileCoinClient {
|
||||
Result<(transaction: Transaction, receipt: Receipt), TransactionPreparationError>
|
||||
) -> Void
|
||||
) {
|
||||
inner.accessAsync {
|
||||
Account.TransactionOperations(
|
||||
account: self.accountLock,
|
||||
fogMerkleProofService: $0.serviceProvider.fogMerkleProofService,
|
||||
fogResolverManager: $0.fogResolverManager,
|
||||
feeFetcher: self.feeFetcher,
|
||||
txOutSelectionStrategy: self.txOutSelectionStrategy,
|
||||
mixinSelectionStrategy: self.mixinSelectionStrategy,
|
||||
targetQueue: self.serialQueue
|
||||
).prepareTransaction(to: recipient, amount: amount, feeLevel: feeLevel) { result in
|
||||
self.callbackQueue.async {
|
||||
completion(result)
|
||||
}
|
||||
Account.TransactionOperations(
|
||||
account: accountLock,
|
||||
fogMerkleProofService: serviceProvider.fogMerkleProofService,
|
||||
fogResolverManager: fogResolverManager,
|
||||
feeFetcher: feeFetcher,
|
||||
txOutSelectionStrategy: txOutSelectionStrategy,
|
||||
mixinSelectionStrategy: mixinSelectionStrategy,
|
||||
targetQueue: serialQueue
|
||||
).prepareTransaction(to: recipient, amount: amount, feeLevel: feeLevel) { result in
|
||||
self.callbackQueue.async {
|
||||
completion(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -189,20 +184,18 @@ public final class MobileCoinClient {
|
||||
feeLevel: FeeLevel = .minimum,
|
||||
completion: @escaping (Result<[Transaction], DefragTransactionPreparationError>) -> Void
|
||||
) {
|
||||
inner.accessAsync {
|
||||
Account.TransactionOperations(
|
||||
account: self.accountLock,
|
||||
fogMerkleProofService: $0.serviceProvider.fogMerkleProofService,
|
||||
fogResolverManager: $0.fogResolverManager,
|
||||
feeFetcher: self.feeFetcher,
|
||||
txOutSelectionStrategy: self.txOutSelectionStrategy,
|
||||
mixinSelectionStrategy: self.mixinSelectionStrategy,
|
||||
targetQueue: self.serialQueue
|
||||
).prepareDefragmentationStepTransactions(toSendAmount: amount, feeLevel: feeLevel)
|
||||
{ result in
|
||||
self.callbackQueue.async {
|
||||
completion(result)
|
||||
}
|
||||
Account.TransactionOperations(
|
||||
account: accountLock,
|
||||
fogMerkleProofService: serviceProvider.fogMerkleProofService,
|
||||
fogResolverManager: fogResolverManager,
|
||||
feeFetcher: feeFetcher,
|
||||
txOutSelectionStrategy: txOutSelectionStrategy,
|
||||
mixinSelectionStrategy: mixinSelectionStrategy,
|
||||
targetQueue: serialQueue
|
||||
).prepareDefragmentationStepTransactions(toSendAmount: amount, feeLevel: feeLevel)
|
||||
{ result in
|
||||
self.callbackQueue.async {
|
||||
completion(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -211,14 +204,12 @@ public final class MobileCoinClient {
|
||||
_ transaction: Transaction,
|
||||
completion: @escaping (Result<(), TransactionSubmissionError>) -> Void
|
||||
) {
|
||||
inner.accessAsync {
|
||||
TransactionSubmitter(
|
||||
consensusService: $0.serviceProvider.consensusService,
|
||||
feeFetcher: self.feeFetcher
|
||||
).submitTransaction(transaction) { result in
|
||||
self.callbackQueue.async {
|
||||
completion(result)
|
||||
}
|
||||
TransactionSubmitter(
|
||||
consensusService: serviceProvider.consensusService,
|
||||
feeFetcher: feeFetcher
|
||||
).submitTransaction(transaction) { result in
|
||||
self.callbackQueue.async {
|
||||
completion(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -227,16 +218,14 @@ public final class MobileCoinClient {
|
||||
of transaction: Transaction,
|
||||
completion: @escaping (Result<TransactionStatus, ConnectionError>) -> Void
|
||||
) {
|
||||
inner.accessAsync {
|
||||
TransactionStatusChecker(
|
||||
account: self.accountLock,
|
||||
fogUntrustedTxOutService: $0.serviceProvider.fogUntrustedTxOutService,
|
||||
fogKeyImageService: $0.serviceProvider.fogKeyImageService,
|
||||
targetQueue: self.serialQueue
|
||||
).checkStatus(transaction) { result in
|
||||
self.callbackQueue.async {
|
||||
completion(result)
|
||||
}
|
||||
TransactionStatusChecker(
|
||||
account: accountLock,
|
||||
fogUntrustedTxOutService: serviceProvider.fogUntrustedTxOutService,
|
||||
fogKeyImageService: serviceProvider.fogKeyImageService,
|
||||
targetQueue: serialQueue
|
||||
).checkStatus(transaction) { result in
|
||||
self.callbackQueue.async {
|
||||
completion(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -307,18 +296,6 @@ extension MobileCoinClient {
|
||||
}
|
||||
}
|
||||
|
||||
extension MobileCoinClient {
|
||||
private struct Inner {
|
||||
let serviceProvider: ServiceProvider
|
||||
let fogResolverManager: FogResolverManager
|
||||
|
||||
init(serviceProvider: ServiceProvider, fogResolverManager: FogResolverManager) {
|
||||
self.serviceProvider = serviceProvider
|
||||
self.fogResolverManager = fogResolverManager
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MobileCoinClient {
|
||||
public struct Config {
|
||||
/// - Returns: `InvalidInputError` when `consensusUrl` or `fogUrl` are not well-formed URLs
|
||||
@ -368,21 +345,41 @@ extension MobileCoinClient {
|
||||
self.networkConfig = networkConfig
|
||||
}
|
||||
|
||||
public var transportProtocol: TransportProtocol {
|
||||
get { networkConfig.transportProtocol }
|
||||
set { networkConfig.transportProtocol = newValue }
|
||||
}
|
||||
|
||||
public mutating func setConsensusTrustRoots(_ trustRoots: [Data])
|
||||
-> Result<(), InvalidInputError>
|
||||
{
|
||||
Self.parseTrustRoots(trustRootsBytes: trustRoots).map { trustRoots in
|
||||
networkConfig.consensusTrustRoots = trustRoots
|
||||
Self.parseTrustRoots(trustRootsBytes: trustRoots).map {
|
||||
networkConfig.consensusTrustRoots = $0
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func setFogTrustRoots(_ trustRoots: [Data]) -> Result<(), InvalidInputError>
|
||||
{
|
||||
Self.parseTrustRoots(trustRootsBytes: trustRoots).map { trustRoots in
|
||||
networkConfig.fogTrustRoots = trustRoots
|
||||
Self.parseTrustRoots(trustRootsBytes: trustRoots).map {
|
||||
networkConfig.fogTrustRoots = $0
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func setConsensusBasicAuthorization(username: String, password: String) {
|
||||
networkConfig.consensusAuthorization =
|
||||
BasicCredentials(username: username, password: password)
|
||||
}
|
||||
|
||||
public mutating func setFogBasicAuthorization(username: String, password: String) {
|
||||
networkConfig.fogUserAuthorization =
|
||||
BasicCredentials(username: username, password: password)
|
||||
}
|
||||
|
||||
public var httpRequester: HttpRequester? {
|
||||
get { networkConfig.httpRequester }
|
||||
set { networkConfig.httpRequester = newValue }
|
||||
}
|
||||
|
||||
private static func parseTrustRoots(trustRootsBytes: [Data])
|
||||
-> Result<[NIOSSLCertificate], InvalidInputError>
|
||||
{
|
||||
@ -400,15 +397,5 @@ extension MobileCoinClient {
|
||||
}
|
||||
return .success(trustRoots)
|
||||
}
|
||||
|
||||
public mutating func setConsensusBasicAuthorization(username: String, password: String) {
|
||||
networkConfig.consensusAuthorization =
|
||||
BasicCredentials(username: username, password: password)
|
||||
}
|
||||
|
||||
public mutating func setFogBasicAuthorization(username: String, password: String) {
|
||||
networkConfig.fogUserAuthorization =
|
||||
BasicCredentials(username: username, password: password)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,25 +6,25 @@ import Foundation
|
||||
import NIOSSL
|
||||
|
||||
protocol AttestedConnectionConfigProtocol: ConnectionConfigProtocol {
|
||||
var url: MobileCoinUrlProtocol { get }
|
||||
var attestation: Attestation { get }
|
||||
var trustRoots: [NIOSSLCertificate]? { get }
|
||||
var authorization: BasicCredentials? { get }
|
||||
}
|
||||
|
||||
struct AttestedConnectionConfig<Url: MobileCoinUrlProtocol>: AttestedConnectionConfigProtocol {
|
||||
let urlTyped: Url
|
||||
let transportProtocolOption: TransportProtocol.Option
|
||||
let attestation: Attestation
|
||||
let trustRoots: [NIOSSLCertificate]?
|
||||
let authorization: BasicCredentials?
|
||||
|
||||
init(
|
||||
url: Url,
|
||||
transportProtocolOption: TransportProtocol.Option,
|
||||
attestation: Attestation,
|
||||
trustRoots: [NIOSSLCertificate]?,
|
||||
authorization: BasicCredentials?
|
||||
) {
|
||||
self.urlTyped = url
|
||||
self.transportProtocolOption = transportProtocolOption
|
||||
self.attestation = attestation
|
||||
self.trustRoots = trustRoots
|
||||
self.authorization = authorization
|
||||
|
||||
@ -3,78 +3,41 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import GRPC
|
||||
|
||||
class ArbitraryConnection {
|
||||
class ArbitraryConnection<GrpcService, HttpService> {
|
||||
private let inner: SerialDispatchLock<Inner>
|
||||
|
||||
init(url: MobileCoinUrlProtocol, targetQueue: DispatchQueue?) {
|
||||
let inner = Inner(url: url)
|
||||
private let connectionOptionWrapperFactory: (TransportProtocol.Option)
|
||||
-> ConnectionOptionWrapper<GrpcService, HttpService>
|
||||
|
||||
init(
|
||||
connectionOptionWrapperFactory: @escaping (TransportProtocol.Option)
|
||||
-> ConnectionOptionWrapper<GrpcService, HttpService>,
|
||||
transportProtocolOption: TransportProtocol.Option,
|
||||
targetQueue: DispatchQueue?
|
||||
) {
|
||||
self.connectionOptionWrapperFactory = connectionOptionWrapperFactory
|
||||
let connectionOptionWrapper = connectionOptionWrapperFactory(transportProtocolOption)
|
||||
let inner = Inner(connectionOptionWrapper: connectionOptionWrapper)
|
||||
self.inner = .init(inner, targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func performCall<Call: GrpcCallable>(
|
||||
_ call: Call,
|
||||
request: Call.Request,
|
||||
completion: @escaping (Result<Call.Response, ConnectionError>) -> Void
|
||||
) {
|
||||
func performCallCallback(callResult: UnaryCallResult<Call.Response>) {
|
||||
inner.accessAsync {
|
||||
let result = $0.processResponse(callResult: callResult)
|
||||
switch result {
|
||||
case .success:
|
||||
logger.info("Call complete. url: \($0.url)", logFunction: false)
|
||||
case .failure(let connectionError):
|
||||
let errorMessage =
|
||||
"Connection failure. url: \($0.url), error: \(connectionError)"
|
||||
switch connectionError {
|
||||
case .connectionFailure, .serverRateLimited:
|
||||
logger.warning(errorMessage, logFunction: false)
|
||||
case .authorizationFailure, .invalidServerResponse,
|
||||
.attestationVerificationFailed, .outdatedClient:
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
}
|
||||
}
|
||||
completion(result)
|
||||
}
|
||||
}
|
||||
inner.accessAsync {
|
||||
logger.info("Performing call... url: \($0.url)", logFunction: false)
|
||||
func setTransportProtocolOption(_ transportProtocolOption: TransportProtocol.Option) {
|
||||
let connectionOptionWrapper = connectionOptionWrapperFactory(transportProtocolOption)
|
||||
inner.accessAsync { $0.connectionOptionWrapper = connectionOptionWrapper }
|
||||
}
|
||||
|
||||
let callOptions = $0.requestCallOptions()
|
||||
call.call(request: request, callOptions: callOptions, completion: performCallCallback)
|
||||
}
|
||||
var connectionOptionWrapper: ConnectionOptionWrapper<GrpcService, HttpService> {
|
||||
inner.accessWithoutLocking.connectionOptionWrapper
|
||||
}
|
||||
}
|
||||
|
||||
extension ArbitraryConnection {
|
||||
private struct Inner {
|
||||
let url: MobileCoinUrlProtocol
|
||||
private let session: ConnectionSession
|
||||
var connectionOptionWrapper: ConnectionOptionWrapper<GrpcService, HttpService>
|
||||
|
||||
init(url: MobileCoinUrlProtocol) {
|
||||
self.url = url
|
||||
self.session = ConnectionSession(url: url)
|
||||
}
|
||||
|
||||
func requestCallOptions() -> CallOptions {
|
||||
var callOptions = CallOptions()
|
||||
session.addRequestHeaders(to: &callOptions.customMetadata)
|
||||
return callOptions
|
||||
}
|
||||
|
||||
func processResponse<Response>(callResult: UnaryCallResult<Response>)
|
||||
-> Result<Response, ConnectionError>
|
||||
{
|
||||
guard callResult.status.isOk, let response = callResult.response else {
|
||||
return .failure(.connectionFailure(String(describing: callResult.status)))
|
||||
}
|
||||
|
||||
if let initialMetadata = callResult.initialMetadata {
|
||||
session.processResponse(headers: initialMetadata)
|
||||
}
|
||||
|
||||
return .success(response)
|
||||
init(connectionOptionWrapper: ConnectionOptionWrapper<GrpcService, HttpService>) {
|
||||
self.connectionOptionWrapper = connectionOptionWrapper
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,363 +2,53 @@
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable closure_body_length cyclomatic_complexity function_body_length
|
||||
// swiftlint:disable multiline_function_chains operator_usage_whitespace
|
||||
|
||||
import Foundation
|
||||
import GRPC
|
||||
import LibMobileCoin
|
||||
|
||||
enum AttestedConnectionError: Error {
|
||||
case connectionError(ConnectionError)
|
||||
case attestationFailure(String = String())
|
||||
}
|
||||
|
||||
extension AttestedConnectionError: CustomStringConvertible {
|
||||
var description: String {
|
||||
"Attested connection error: " + {
|
||||
switch self {
|
||||
case .connectionError(let connectionError):
|
||||
return "\(connectionError)"
|
||||
case .attestationFailure(let reason):
|
||||
return "Attestation failure\(!reason.isEmpty ? ": \(reason)" : "")"
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
class AttestedConnection {
|
||||
private let inner: SerialCallbackLock<Inner>
|
||||
|
||||
init(
|
||||
client: AttestableGrpcClient,
|
||||
config: AttestedConnectionConfigProtocol,
|
||||
targetQueue: DispatchQueue?,
|
||||
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
|
||||
rngContext: Any? = nil
|
||||
) {
|
||||
let inner = Inner(client: client, config: config, rng: rng, rngContext: rngContext)
|
||||
self.inner = .init(inner, targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func setAuthorization(credentials: BasicCredentials) {
|
||||
inner.priorityAccessAsync {
|
||||
$0.setAuthorization(credentials: credentials)
|
||||
}
|
||||
}
|
||||
|
||||
func performAttestedCall<Call: AttestedGrpcCallable>(
|
||||
_ call: Call,
|
||||
request: Call.InnerRequest,
|
||||
completion: @escaping (Result<Call.InnerResponse, ConnectionError>) -> Void
|
||||
) where Call.InnerRequestAad == (), Call.InnerResponseAad == () {
|
||||
performAttestedCall(call, requestAad: (), request: request, completion: completion)
|
||||
}
|
||||
|
||||
func performAttestedCall<Call: AttestedGrpcCallable>(
|
||||
_ call: Call,
|
||||
requestAad: Call.InnerRequestAad,
|
||||
request: Call.InnerRequest,
|
||||
completion: @escaping (Result<Call.InnerResponse, ConnectionError>) -> Void
|
||||
) where Call.InnerResponseAad == () {
|
||||
performAttestedCall(call, requestAad: requestAad, request: request) {
|
||||
completion($0.map { $0.response })
|
||||
}
|
||||
}
|
||||
|
||||
func performAttestedCall<Call: AttestedGrpcCallable>(
|
||||
_ call: Call,
|
||||
request: Call.InnerRequest,
|
||||
completion: @escaping (
|
||||
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
|
||||
ConnectionError>
|
||||
) -> Void
|
||||
) where Call.InnerRequestAad == () {
|
||||
performAttestedCall(call, requestAad: (), request: request, completion: completion)
|
||||
}
|
||||
|
||||
func performAttestedCall<Call: AttestedGrpcCallable>(
|
||||
_ call: Call,
|
||||
requestAad: Call.InnerRequestAad,
|
||||
request: Call.InnerRequest,
|
||||
completion: @escaping (
|
||||
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
|
||||
ConnectionError>
|
||||
) -> Void
|
||||
) {
|
||||
inner.accessAsync(block: { inner, callback in
|
||||
inner.performAttestedCallWithAuth(
|
||||
call,
|
||||
requestAad: requestAad,
|
||||
request: request,
|
||||
completion: callback)
|
||||
}, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension AttestedConnection {
|
||||
// Note: Because `SerialCallbackLock` is being used to wrap `AttestedConnection.Inner`, calls
|
||||
// to `AttestedConnection.Inner` have exclusive access (other calls will be queued up) until the
|
||||
// executing call invokes the completion handler that returns control back to
|
||||
// `AttestedConnection`, at which point the block passed to the async `SerialCallbackLock`
|
||||
// method that invoked the call to inner will complete and the next async `SerialCallbackLock`
|
||||
// access block will execute.
|
||||
//
|
||||
// This means that calls to `AttestedConnection.Inner` can assume thread-safety until the call
|
||||
// invokes the completion handler.
|
||||
private struct Inner {
|
||||
private let url: MobileCoinUrlProtocol
|
||||
private let session: ConnectionSession
|
||||
private let client: AttestableGrpcClient
|
||||
private let attestAke: AttestAke
|
||||
|
||||
private let responderId: String
|
||||
private let attestationVerifier: AttestationVerifier
|
||||
private let rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)?
|
||||
private let rngContext: Any?
|
||||
|
||||
init(
|
||||
client: AttestableGrpcClient,
|
||||
config: AttestedConnectionConfigProtocol,
|
||||
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
|
||||
rngContext: Any? = nil
|
||||
) {
|
||||
self.url = config.url
|
||||
self.session = ConnectionSession(config: config)
|
||||
self.client = client
|
||||
self.attestAke = AttestAke()
|
||||
self.responderId = config.url.responderId
|
||||
self.attestationVerifier = AttestationVerifier(attestation: config.attestation)
|
||||
self.rng = rng
|
||||
self.rngContext = rngContext
|
||||
}
|
||||
|
||||
func setAuthorization(credentials: BasicCredentials) {
|
||||
session.authorizationCredentials = credentials
|
||||
}
|
||||
|
||||
func performAttestedCallWithAuth<Call: AttestedGrpcCallable>(
|
||||
_ call: Call,
|
||||
requestAad: Call.InnerRequestAad,
|
||||
request: Call.InnerRequest,
|
||||
completion: @escaping (
|
||||
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
|
||||
ConnectionError>
|
||||
) -> Void
|
||||
) {
|
||||
doPerformAttestedCallWithAuth(
|
||||
call,
|
||||
requestAad: requestAad,
|
||||
request: request,
|
||||
attestAkeCipher: attestAke.cipher.map { ($0, freshCipher: false) },
|
||||
completion: completion)
|
||||
}
|
||||
|
||||
private func doPerformAttestedCallWithAuth<Call: AttestedGrpcCallable>(
|
||||
_ call: Call,
|
||||
requestAad: Call.InnerRequestAad,
|
||||
request: Call.InnerRequest,
|
||||
attestAkeCipher: (AttestAke.Cipher, freshCipher: Bool)?,
|
||||
completion: @escaping (
|
||||
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
|
||||
ConnectionError>
|
||||
) -> Void
|
||||
) {
|
||||
if let (attestAkeCipher, freshCipher) = attestAkeCipher {
|
||||
logger.info(
|
||||
"Performing attested call... url: \(self.url)",
|
||||
logFunction: false)
|
||||
|
||||
doPerformAttestedCall(
|
||||
call,
|
||||
requestAad: requestAad,
|
||||
request: request,
|
||||
attestAkeCipher: attestAkeCipher
|
||||
) {
|
||||
switch $0 {
|
||||
case .success(let response):
|
||||
logger.info(
|
||||
"Attested call successful. url: \(self.url)",
|
||||
logFunction: false)
|
||||
|
||||
completion(.success(response))
|
||||
case .failure(.connectionError(let connectionError)):
|
||||
let errorMessage = "Connection failure while performing attested call. " +
|
||||
"url: \(self.url), error: \(connectionError)"
|
||||
switch connectionError {
|
||||
case .connectionFailure, .serverRateLimited:
|
||||
logger.warning(errorMessage, logFunction: false)
|
||||
case .authorizationFailure, .invalidServerResponse,
|
||||
.attestationVerificationFailed, .outdatedClient:
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
}
|
||||
|
||||
completion(.failure(connectionError))
|
||||
case .failure(.attestationFailure):
|
||||
self.attestAke.deattest()
|
||||
|
||||
if freshCipher {
|
||||
let errorMessage =
|
||||
"Attestation failure with fresh auth. url: \(self.url)"
|
||||
logger.warning(errorMessage, logFunction: false)
|
||||
|
||||
completion(.failure(.invalidServerResponse(errorMessage)))
|
||||
} else {
|
||||
logger.info(
|
||||
"Attestation failure using cached auth, reattesting... url: " +
|
||||
"\(self.url)",
|
||||
logFunction: false)
|
||||
|
||||
self.doPerformAttestedCallWithAuth(
|
||||
call,
|
||||
requestAad: requestAad,
|
||||
request: request,
|
||||
attestAkeCipher: nil,
|
||||
completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.info(
|
||||
"Peforming attestation... url: \(url)",
|
||||
logFunction: false)
|
||||
|
||||
doPerformAuthCall {
|
||||
switch $0 {
|
||||
case .success(let attestAkeCipher):
|
||||
logger.info(
|
||||
"Attestation successful. url: \(self.url)",
|
||||
logFunction: false)
|
||||
|
||||
self.doPerformAttestedCallWithAuth(
|
||||
call,
|
||||
requestAad: requestAad,
|
||||
request: request,
|
||||
attestAkeCipher: (attestAkeCipher, freshCipher: true),
|
||||
completion: completion)
|
||||
case .failure(let connectionError):
|
||||
let errorMessage = "Connection failure while performing attestation. " +
|
||||
"url: \(self.url), error: \(connectionError)"
|
||||
switch connectionError {
|
||||
case .connectionFailure, .serverRateLimited:
|
||||
logger.warning(errorMessage, logFunction: false)
|
||||
case .authorizationFailure, .invalidServerResponse,
|
||||
.attestationVerificationFailed, .outdatedClient:
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
}
|
||||
|
||||
completion(.failure(connectionError))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func doPerformAuthCall(
|
||||
completion: @escaping (Result<AttestAke.Cipher, ConnectionError>) -> Void
|
||||
) {
|
||||
let request = attestAke.authBeginRequest(
|
||||
responderId: responderId,
|
||||
rng: rng,
|
||||
rngContext: rngContext)
|
||||
|
||||
doPerformCall(
|
||||
AuthGrpcCallableWrapper(authCallable: client.authCallable),
|
||||
request: request
|
||||
) {
|
||||
completion(
|
||||
$0.mapError {
|
||||
switch $0 {
|
||||
case .connectionError(let connectionError):
|
||||
return connectionError
|
||||
case .attestationFailure:
|
||||
self.attestAke.deattest()
|
||||
|
||||
return .invalidServerResponse(
|
||||
"Attestation failure during auth. url: \(self.url)")
|
||||
}
|
||||
}.flatMap { response in
|
||||
self.attestAke.authEnd(
|
||||
authResponse: response,
|
||||
attestationVerifier: self.attestationVerifier
|
||||
).mapError {
|
||||
switch $0 {
|
||||
case .invalidInput(let reason):
|
||||
return .invalidServerResponse(reason)
|
||||
case .attestationVerificationFailed(let reason):
|
||||
return .attestationVerificationFailed(reason)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func doPerformAttestedCall<Call: AttestedGrpcCallable>(
|
||||
_ call: Call,
|
||||
requestAad: Call.InnerRequestAad,
|
||||
request: Call.InnerRequest,
|
||||
attestAkeCipher: AttestAke.Cipher,
|
||||
completion: @escaping (
|
||||
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
|
||||
AttestedConnectionError>
|
||||
) -> Void
|
||||
) {
|
||||
guard let processedRequest =
|
||||
call.processRequest(
|
||||
requestAad: requestAad,
|
||||
request: request,
|
||||
attestAkeCipher: attestAkeCipher)
|
||||
.mapError({ _ in .attestationFailure() })
|
||||
.successOr(completion: completion)
|
||||
else { return }
|
||||
|
||||
doPerformCall(call, request: processedRequest) {
|
||||
completion($0.flatMap { response in
|
||||
call.processResponse(response: response, attestAkeCipher: attestAkeCipher)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func doPerformCall<Call: GrpcCallable>(
|
||||
_ call: Call,
|
||||
request: Call.Request,
|
||||
completion: @escaping (Result<Call.Response, AttestedConnectionError>) -> Void
|
||||
) {
|
||||
let callOptions = requestCallOptions()
|
||||
|
||||
call.call(request: request, callOptions: callOptions) {
|
||||
completion(self.processResponse(callResult: $0))
|
||||
}
|
||||
}
|
||||
|
||||
private func requestCallOptions() -> CallOptions {
|
||||
var callOptions = CallOptions()
|
||||
session.addRequestHeaders(to: &callOptions.customMetadata)
|
||||
return callOptions
|
||||
}
|
||||
|
||||
private func processResponse<Response>(callResult: UnaryCallResult<Response>)
|
||||
-> Result<Response, AttestedConnectionError>
|
||||
{
|
||||
// Basic credential authorization failure
|
||||
guard callResult.status.code != .unauthenticated else {
|
||||
return .failure(.connectionError(.authorizationFailure("url: \(url)")))
|
||||
}
|
||||
|
||||
// Attestation failure, reattest
|
||||
guard callResult.status.code != .permissionDenied else {
|
||||
return .failure(.attestationFailure())
|
||||
}
|
||||
|
||||
guard callResult.status.isOk, let response = callResult.response else {
|
||||
return .failure(.connectionError(
|
||||
.connectionFailure("url: \(url), status: \(callResult.status)")))
|
||||
}
|
||||
|
||||
if let initialMetadata = callResult.initialMetadata {
|
||||
session.processResponse(headers: initialMetadata)
|
||||
}
|
||||
|
||||
return .success(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
//class AttestedConnection<Service> {
|
||||
// private let inner: SerialDispatchLock<Inner>
|
||||
//
|
||||
// init(connectionOptionWrapper: ConnectionOptionWrapper<Service>, targetQueue: DispatchQueue?) {
|
||||
// let inner = Inner(connectionOptionWrapper: connectionOptionWrapper)
|
||||
// self.inner = .init(inner, targetQueue: targetQueue)
|
||||
// }
|
||||
//
|
||||
// func setConnectionOptionWrapper(_ connectionOptionWrapper: ConnectionOptionWrapper<Service>) {
|
||||
// inner.accessAsync { $0.connectionOptionWrapper = connectionOptionWrapper }
|
||||
// }
|
||||
//
|
||||
// func setAuthorization(credentials: BasicCredentials) {
|
||||
// inner.accessAsync { $0.setAuthorization(credentials: credentials) }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//extension AttestedConnection {
|
||||
// private struct Inner {
|
||||
// var connectionOptionWrapper: ConnectionOptionWrapper<ServiceType> {
|
||||
// didSet {
|
||||
// if let credentials = authorizationCredentials {
|
||||
// switch connectionOptionWrapper {
|
||||
// case .grpc(grpcService: let grpcService):
|
||||
// grpcService.setAuthorization(credentials: credentials)
|
||||
// case .http(httpService: let httpService):
|
||||
// httpService.setAuthorization(credentials: credentials)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// private var authorizationCredentials: BasicCredentials?
|
||||
//
|
||||
// init(connectionOptionWrapper: ConnectionOptionWrapper<ServiceType>) {
|
||||
// self.connectionOptionWrapper = connectionOptionWrapper
|
||||
// }
|
||||
//
|
||||
// mutating func setAuthorization(credentials: BasicCredentials) {
|
||||
// self.authorizationCredentials = credentials
|
||||
// switch connectionOptionWrapper {
|
||||
// case .grpc(grpcService: let grpcService):
|
||||
// grpcService.setAuthorization(credentials: credentials)
|
||||
// case .http(httpService: let httpService):
|
||||
// httpService.setAuthorization(credentials: credentials)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
@ -3,100 +3,68 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import GRPC
|
||||
|
||||
class Connection {
|
||||
class Connection<GrpcService: ConnectionProtocol, HttpService: ConnectionProtocol> {
|
||||
private let inner: SerialDispatchLock<Inner>
|
||||
|
||||
init(config: ConnectionConfigProtocol, targetQueue: DispatchQueue?) {
|
||||
let inner = Inner(config: config)
|
||||
private let connectionOptionWrapperFactory: (TransportProtocol.Option)
|
||||
-> ConnectionOptionWrapper<GrpcService, HttpService>
|
||||
|
||||
init(
|
||||
connectionOptionWrapperFactory: @escaping (TransportProtocol.Option)
|
||||
-> ConnectionOptionWrapper<GrpcService, HttpService>,
|
||||
transportProtocolOption: TransportProtocol.Option,
|
||||
targetQueue: DispatchQueue?
|
||||
) {
|
||||
self.connectionOptionWrapperFactory = connectionOptionWrapperFactory
|
||||
let connectionOptionWrapper = connectionOptionWrapperFactory(transportProtocolOption)
|
||||
let inner = Inner(connectionOptionWrapper: connectionOptionWrapper)
|
||||
self.inner = .init(inner, targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func setTransportProtocolOption(_ transportProtocolOption: TransportProtocol.Option) {
|
||||
let connectionOptionWrapper = connectionOptionWrapperFactory(transportProtocolOption)
|
||||
inner.accessAsync { $0.connectionOptionWrapper = connectionOptionWrapper }
|
||||
}
|
||||
|
||||
func setAuthorization(credentials: BasicCredentials) {
|
||||
inner.accessAsync {
|
||||
$0.setAuthorization(credentials: credentials)
|
||||
}
|
||||
inner.accessAsync { $0.setAuthorization(credentials: credentials) }
|
||||
}
|
||||
|
||||
func performCall<Call: GrpcCallable>(
|
||||
_ call: Call,
|
||||
request: Call.Request,
|
||||
completion: @escaping (Result<Call.Response, ConnectionError>) -> Void
|
||||
) {
|
||||
func performCallCallback(callResult: UnaryCallResult<Call.Response>) {
|
||||
inner.accessAsync {
|
||||
let result = $0.processResponse(callResult: callResult)
|
||||
switch result {
|
||||
case .success:
|
||||
logger.info("Call complete. url: \($0.url)", logFunction: false)
|
||||
case .failure(let connectionError):
|
||||
let errorMessage =
|
||||
"Connection failure. url: \($0.url), error: \(connectionError)"
|
||||
switch connectionError {
|
||||
case .connectionFailure, .serverRateLimited:
|
||||
logger.warning(errorMessage, logFunction: false)
|
||||
case .authorizationFailure, .invalidServerResponse,
|
||||
.attestationVerificationFailed, .outdatedClient:
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
}
|
||||
}
|
||||
completion(result)
|
||||
}
|
||||
}
|
||||
|
||||
inner.accessAsync {
|
||||
logger.info("Performing call... url: \($0.url)", logFunction: false)
|
||||
|
||||
let callOptions = $0.requestCallOptions()
|
||||
call.call(request: request, callOptions: callOptions, completion: performCallCallback)
|
||||
}
|
||||
}
|
||||
|
||||
func performCall<Call: GrpcCallable>(
|
||||
_ call: Call,
|
||||
completion: @escaping (Result<Call.Response, ConnectionError>) -> Void
|
||||
) where Call.Request == () {
|
||||
performCall(call, request: (), completion: completion)
|
||||
var connectionOptionWrapper: ConnectionOptionWrapper<GrpcService, HttpService> {
|
||||
inner.accessWithoutLocking.connectionOptionWrapper
|
||||
}
|
||||
}
|
||||
|
||||
extension Connection {
|
||||
private struct Inner {
|
||||
let url: MobileCoinUrlProtocol
|
||||
private let session: ConnectionSession
|
||||
|
||||
init(config: ConnectionConfigProtocol) {
|
||||
self.url = config.url
|
||||
self.session = ConnectionSession(config: config)
|
||||
var connectionOptionWrapper: ConnectionOptionWrapper<GrpcService, HttpService> {
|
||||
didSet {
|
||||
if let credentials = authorizationCredentials {
|
||||
switch connectionOptionWrapper {
|
||||
case .grpc(grpcService: let grpcService):
|
||||
grpcService.setAuthorization(credentials: credentials)
|
||||
case .http(httpService: let httpService):
|
||||
httpService.setAuthorization(credentials: credentials)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setAuthorization(credentials: BasicCredentials) {
|
||||
session.authorizationCredentials = credentials
|
||||
private var authorizationCredentials: BasicCredentials?
|
||||
|
||||
init(connectionOptionWrapper: ConnectionOptionWrapper<GrpcService, HttpService>) {
|
||||
self.connectionOptionWrapper = connectionOptionWrapper
|
||||
}
|
||||
|
||||
func requestCallOptions() -> CallOptions {
|
||||
var callOptions = CallOptions()
|
||||
session.addRequestHeaders(to: &callOptions.customMetadata)
|
||||
return callOptions
|
||||
}
|
||||
|
||||
func processResponse<Response>(callResult: UnaryCallResult<Response>)
|
||||
-> Result<Response, ConnectionError>
|
||||
{
|
||||
guard callResult.status.code != .unauthenticated else {
|
||||
return .failure(.authorizationFailure("url: \(url)"))
|
||||
mutating func setAuthorization(credentials: BasicCredentials) {
|
||||
self.authorizationCredentials = credentials
|
||||
switch connectionOptionWrapper {
|
||||
case .grpc(grpcService: let grpcService):
|
||||
grpcService.setAuthorization(credentials: credentials)
|
||||
case .http(httpService: let httpService):
|
||||
httpService.setAuthorization(credentials: credentials)
|
||||
}
|
||||
|
||||
guard callResult.status.isOk, let response = callResult.response else {
|
||||
return .failure(.connectionFailure("url: \(url), status: \(callResult.status)"))
|
||||
}
|
||||
|
||||
if let initialMetadata = callResult.initialMetadata {
|
||||
session.processResponse(headers: initialMetadata)
|
||||
}
|
||||
|
||||
return .success(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
10
Sources/Network/Connection/ConnectionOptionWrapper.swift
Normal file
10
Sources/Network/Connection/ConnectionOptionWrapper.swift
Normal file
@ -0,0 +1,10 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum ConnectionOptionWrapper<GrpcService, HttpService> {
|
||||
case grpc(grpcService: GrpcService)
|
||||
case http(httpService: HttpService)
|
||||
}
|
||||
9
Sources/Network/Connection/ConnectionProtocol.swift
Normal file
9
Sources/Network/Connection/ConnectionProtocol.swift
Normal file
@ -0,0 +1,9 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol ConnectionProtocol {
|
||||
func setAuthorization(credentials: BasicCredentials)
|
||||
}
|
||||
@ -7,39 +7,48 @@ import GRPC
|
||||
import LibMobileCoin
|
||||
import SwiftProtobuf
|
||||
|
||||
final class BlockchainConnection: Connection, BlockchainService {
|
||||
private let client: ConsensusCommon_BlockchainAPIClient
|
||||
final class BlockchainConnection:
|
||||
Connection<BlockchainGrpcConnection, BlockchainHttpConnection>, BlockchainService
|
||||
{
|
||||
private let config: ConnectionConfig<ConsensusUrl>
|
||||
private let channelManager: GrpcChannelManager
|
||||
private let targetQueue: DispatchQueue?
|
||||
|
||||
init(
|
||||
config: ConnectionConfig<ConsensusUrl>,
|
||||
channelManager: GrpcChannelManager,
|
||||
targetQueue: DispatchQueue?
|
||||
) {
|
||||
let channel = channelManager.channel(for: config)
|
||||
self.client = ConsensusCommon_BlockchainAPIClient(channel: channel)
|
||||
super.init(config: config, targetQueue: targetQueue)
|
||||
self.config = config
|
||||
self.channelManager = channelManager
|
||||
self.targetQueue = targetQueue
|
||||
|
||||
super.init(
|
||||
connectionOptionWrapperFactory: { transportProtocolOption in
|
||||
switch transportProtocolOption {
|
||||
case .grpc:
|
||||
return .grpc(
|
||||
grpcService: BlockchainGrpcConnection(
|
||||
config: config,
|
||||
channelManager: channelManager,
|
||||
targetQueue: targetQueue))
|
||||
case .http:
|
||||
return .http(httpService: BlockchainHttpConnection())
|
||||
}
|
||||
},
|
||||
transportProtocolOption: config.transportProtocolOption,
|
||||
targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func getLastBlockInfo(
|
||||
completion:
|
||||
@escaping (Result<ConsensusCommon_LastBlockInfoResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
performCall(GetLastBlockInfoCall(client: client), completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension BlockchainConnection {
|
||||
private struct GetLastBlockInfoCall: GrpcCallable {
|
||||
let client: ConsensusCommon_BlockchainAPIClient
|
||||
|
||||
func call(
|
||||
request: (),
|
||||
callOptions: CallOptions?,
|
||||
completion: @escaping (UnaryCallResult<ConsensusCommon_LastBlockInfoResponse>) -> Void
|
||||
) {
|
||||
let unaryCall =
|
||||
client.getLastBlockInfo(Google_Protobuf_Empty(), callOptions: callOptions)
|
||||
unaryCall.callResult.whenSuccess(completion)
|
||||
switch connectionOptionWrapper {
|
||||
case .grpc(let grpcConnection):
|
||||
grpcConnection.getLastBlockInfo(completion: completion)
|
||||
case .http(let httpConnection):
|
||||
httpConnection.getLastBlockInfo(completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,8 +6,14 @@ import Foundation
|
||||
import GRPC
|
||||
import LibMobileCoin
|
||||
|
||||
final class ConsensusConnection: AttestedConnection, ConsensusService {
|
||||
private let client: ConsensusClient_ConsensusClientAPIClient
|
||||
final class ConsensusConnection:
|
||||
Connection<ConsensusGrpcConnection, ConsensusHttpConnection>, ConsensusService
|
||||
{
|
||||
private let config: AttestedConnectionConfig<ConsensusUrl>
|
||||
private let channelManager: GrpcChannelManager
|
||||
private let targetQueue: DispatchQueue?
|
||||
private let rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)?
|
||||
private let rngContext: Any?
|
||||
|
||||
init(
|
||||
config: AttestedConnectionConfig<ConsensusUrl>,
|
||||
@ -16,43 +22,40 @@ final class ConsensusConnection: AttestedConnection, ConsensusService {
|
||||
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
|
||||
rngContext: Any? = nil
|
||||
) {
|
||||
let channel = channelManager.channel(for: config)
|
||||
self.client = ConsensusClient_ConsensusClientAPIClient(channel: channel)
|
||||
self.config = config
|
||||
self.channelManager = channelManager
|
||||
self.targetQueue = targetQueue
|
||||
self.rng = rng
|
||||
self.rngContext = rngContext
|
||||
|
||||
super.init(
|
||||
client: Attest_AttestedApiClient(channel: channel),
|
||||
config: config,
|
||||
targetQueue: targetQueue,
|
||||
rng: rng,
|
||||
rngContext: rngContext)
|
||||
connectionOptionWrapperFactory: { transportProtocolOption in
|
||||
switch transportProtocolOption {
|
||||
case .grpc:
|
||||
return .grpc(
|
||||
grpcService: ConsensusGrpcConnection(
|
||||
config: config,
|
||||
channelManager: channelManager,
|
||||
targetQueue: targetQueue,
|
||||
rng: rng,
|
||||
rngContext: rngContext))
|
||||
case .http:
|
||||
return .http(httpService: ConsensusHttpConnection())
|
||||
}
|
||||
},
|
||||
transportProtocolOption: config.transportProtocolOption,
|
||||
targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func proposeTx(
|
||||
_ tx: External_Tx,
|
||||
completion: @escaping (Result<ConsensusCommon_ProposeTxResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
performAttestedCall(
|
||||
ProposeTxCall(client: client),
|
||||
request: tx,
|
||||
completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension ConsensusConnection {
|
||||
private struct ProposeTxCall: AttestedGrpcCallable {
|
||||
typealias InnerRequest = External_Tx
|
||||
typealias InnerResponse = ConsensusCommon_ProposeTxResponse
|
||||
|
||||
let client: ConsensusClient_ConsensusClientAPIClient
|
||||
|
||||
func call(
|
||||
request: Attest_Message,
|
||||
callOptions: CallOptions?,
|
||||
completion: @escaping (UnaryCallResult<ConsensusCommon_ProposeTxResponse>) -> Void
|
||||
) {
|
||||
let unaryCall = client.clientTxPropose(request, callOptions: callOptions)
|
||||
unaryCall.callResult.whenSuccess(completion)
|
||||
switch connectionOptionWrapper {
|
||||
case .grpc(let grpcConnection):
|
||||
grpcConnection.proposeTx(tx, completion: completion)
|
||||
case .http(let httpConnection):
|
||||
httpConnection.proposeTx(tx, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Attest_AttestedApiClient: AuthGrpcCallableClient {}
|
||||
|
||||
@ -6,38 +6,48 @@ import Foundation
|
||||
import GRPC
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogBlockConnection: Connection, FogBlockService {
|
||||
private let client: FogLedger_FogBlockAPIClient
|
||||
final class FogBlockConnection:
|
||||
Connection<FogBlockGrpcConnection, FogBlockHttpConnection>, FogBlockService
|
||||
{
|
||||
private let config: ConnectionConfig<FogUrl>
|
||||
private let channelManager: GrpcChannelManager
|
||||
private let targetQueue: DispatchQueue?
|
||||
|
||||
init(
|
||||
config: ConnectionConfig<FogUrl>,
|
||||
channelManager: GrpcChannelManager,
|
||||
targetQueue: DispatchQueue?
|
||||
) {
|
||||
let channel = channelManager.channel(for: config)
|
||||
self.client = FogLedger_FogBlockAPIClient(channel: channel)
|
||||
super.init(config: config, targetQueue: targetQueue)
|
||||
self.config = config
|
||||
self.channelManager = channelManager
|
||||
self.targetQueue = targetQueue
|
||||
|
||||
super.init(
|
||||
connectionOptionWrapperFactory: { transportProtocolOption in
|
||||
switch transportProtocolOption {
|
||||
case .grpc:
|
||||
return .grpc(
|
||||
grpcService: FogBlockGrpcConnection(
|
||||
config: config,
|
||||
channelManager: channelManager,
|
||||
targetQueue: targetQueue))
|
||||
case .http:
|
||||
return .http(httpService: FogBlockHttpConnection())
|
||||
}
|
||||
},
|
||||
transportProtocolOption: config.transportProtocolOption,
|
||||
targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func getBlocks(
|
||||
request: FogLedger_BlockRequest,
|
||||
completion: @escaping (Result<FogLedger_BlockResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
performCall(GetBlocksCall(client: client), request: request, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension FogBlockConnection {
|
||||
private struct GetBlocksCall: GrpcCallable {
|
||||
let client: FogLedger_FogBlockAPIClient
|
||||
|
||||
func call(
|
||||
request: FogLedger_BlockRequest,
|
||||
callOptions: CallOptions?,
|
||||
completion: @escaping (UnaryCallResult<FogLedger_BlockResponse>) -> Void
|
||||
) {
|
||||
let unaryCall = client.getBlocks(request, callOptions: callOptions)
|
||||
unaryCall.callResult.whenSuccess(completion)
|
||||
switch connectionOptionWrapper {
|
||||
case .grpc(let grpcConnection):
|
||||
grpcConnection.getBlocks(request: request, completion: completion)
|
||||
case .http(let httpConnection):
|
||||
httpConnection.getBlocks(request: request, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,8 +6,14 @@ import Foundation
|
||||
import GRPC
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogKeyImageConnection: AttestedConnection, FogKeyImageService {
|
||||
private let client: FogLedger_FogKeyImageAPIClient
|
||||
final class FogKeyImageConnection:
|
||||
Connection<FogKeyImageGrpcConnection, FogKeyImageHttpConnection>, FogKeyImageService
|
||||
{
|
||||
private let config: AttestedConnectionConfig<FogUrl>
|
||||
private let channelManager: GrpcChannelManager
|
||||
private let targetQueue: DispatchQueue?
|
||||
private let rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)?
|
||||
private let rngContext: Any?
|
||||
|
||||
init(
|
||||
config: AttestedConnectionConfig<FogUrl>,
|
||||
@ -16,43 +22,40 @@ final class FogKeyImageConnection: AttestedConnection, FogKeyImageService {
|
||||
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
|
||||
rngContext: Any? = nil
|
||||
) {
|
||||
let channel = channelManager.channel(for: config)
|
||||
self.client = FogLedger_FogKeyImageAPIClient(channel: channel)
|
||||
self.config = config
|
||||
self.channelManager = channelManager
|
||||
self.targetQueue = targetQueue
|
||||
self.rng = rng
|
||||
self.rngContext = rngContext
|
||||
|
||||
super.init(
|
||||
client: self.client,
|
||||
config: config,
|
||||
targetQueue: targetQueue,
|
||||
rng: rng,
|
||||
rngContext: rngContext)
|
||||
connectionOptionWrapperFactory: { transportProtocolOption in
|
||||
switch transportProtocolOption {
|
||||
case .grpc:
|
||||
return .grpc(
|
||||
grpcService: FogKeyImageGrpcConnection(
|
||||
config: config,
|
||||
channelManager: channelManager,
|
||||
targetQueue: targetQueue,
|
||||
rng: rng,
|
||||
rngContext: rngContext))
|
||||
case .http:
|
||||
return .http(httpService: FogKeyImageHttpConnection())
|
||||
}
|
||||
},
|
||||
transportProtocolOption: config.transportProtocolOption,
|
||||
targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func checkKeyImages(
|
||||
request: FogLedger_CheckKeyImagesRequest,
|
||||
completion: @escaping (Result<FogLedger_CheckKeyImagesResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
performAttestedCall(
|
||||
CheckKeyImagesCall(client: client),
|
||||
request: request,
|
||||
completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension FogKeyImageConnection {
|
||||
private struct CheckKeyImagesCall: AttestedGrpcCallable {
|
||||
typealias InnerRequest = FogLedger_CheckKeyImagesRequest
|
||||
typealias InnerResponse = FogLedger_CheckKeyImagesResponse
|
||||
|
||||
let client: FogLedger_FogKeyImageAPIClient
|
||||
|
||||
func call(
|
||||
request: Attest_Message,
|
||||
callOptions: CallOptions?,
|
||||
completion: @escaping (UnaryCallResult<Attest_Message>) -> Void
|
||||
) {
|
||||
let unaryCall = client.checkKeyImages(request, callOptions: callOptions)
|
||||
unaryCall.callResult.whenSuccess(completion)
|
||||
switch connectionOptionWrapper {
|
||||
case .grpc(let grpcConnection):
|
||||
grpcConnection.checkKeyImages(request: request, completion: completion)
|
||||
case .http(let httpConnection):
|
||||
httpConnection.checkKeyImages(request: request, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension FogLedger_FogKeyImageAPIClient: AuthGrpcCallableClient {}
|
||||
|
||||
@ -6,8 +6,14 @@ import Foundation
|
||||
import GRPC
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogMerkleProofConnection: AttestedConnection, FogMerkleProofService {
|
||||
private let client: FogLedger_FogMerkleProofAPIClient
|
||||
final class FogMerkleProofConnection:
|
||||
Connection<FogMerkleProofGrpcConnection, FogMerkleProofHttpConnection>, FogMerkleProofService
|
||||
{
|
||||
private let config: AttestedConnectionConfig<FogUrl>
|
||||
private let channelManager: GrpcChannelManager
|
||||
private let targetQueue: DispatchQueue?
|
||||
private let rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)?
|
||||
private let rngContext: Any?
|
||||
|
||||
init(
|
||||
config: AttestedConnectionConfig<FogUrl>,
|
||||
@ -16,43 +22,40 @@ final class FogMerkleProofConnection: AttestedConnection, FogMerkleProofService
|
||||
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
|
||||
rngContext: Any? = nil
|
||||
) {
|
||||
let channel = channelManager.channel(for: config)
|
||||
self.client = FogLedger_FogMerkleProofAPIClient(channel: channel)
|
||||
self.config = config
|
||||
self.channelManager = channelManager
|
||||
self.targetQueue = targetQueue
|
||||
self.rng = rng
|
||||
self.rngContext = rngContext
|
||||
|
||||
super.init(
|
||||
client: self.client,
|
||||
config: config,
|
||||
targetQueue: targetQueue,
|
||||
rng: rng,
|
||||
rngContext: rngContext)
|
||||
connectionOptionWrapperFactory: { transportProtocolOption in
|
||||
switch transportProtocolOption {
|
||||
case .grpc:
|
||||
return .grpc(
|
||||
grpcService: FogMerkleProofGrpcConnection(
|
||||
config: config,
|
||||
channelManager: channelManager,
|
||||
targetQueue: targetQueue,
|
||||
rng: rng,
|
||||
rngContext: rngContext))
|
||||
case .http:
|
||||
return .http(httpService: FogMerkleProofHttpConnection())
|
||||
}
|
||||
},
|
||||
transportProtocolOption: config.transportProtocolOption,
|
||||
targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func getOutputs(
|
||||
request: FogLedger_GetOutputsRequest,
|
||||
completion: @escaping (Result<FogLedger_GetOutputsResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
performAttestedCall(
|
||||
GetOutputsCall(client: client),
|
||||
request: request,
|
||||
completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension FogMerkleProofConnection {
|
||||
private struct GetOutputsCall: AttestedGrpcCallable {
|
||||
typealias InnerRequest = FogLedger_GetOutputsRequest
|
||||
typealias InnerResponse = FogLedger_GetOutputsResponse
|
||||
|
||||
let client: FogLedger_FogMerkleProofAPIClient
|
||||
|
||||
func call(
|
||||
request: Attest_Message,
|
||||
callOptions: CallOptions?,
|
||||
completion: @escaping (UnaryCallResult<Attest_Message>) -> Void
|
||||
) {
|
||||
let unaryCall = client.getOutputs(request, callOptions: callOptions)
|
||||
unaryCall.callResult.whenSuccess(completion)
|
||||
switch connectionOptionWrapper {
|
||||
case .grpc(let grpcConnection):
|
||||
grpcConnection.getOutputs(request: request, completion: completion)
|
||||
case .http(let httpConnection):
|
||||
httpConnection.getOutputs(request: request, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension FogLedger_FogMerkleProofAPIClient: AuthGrpcCallableClient {}
|
||||
|
||||
@ -6,34 +6,49 @@ import Foundation
|
||||
import GRPC
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogReportConnection: ArbitraryConnection, FogReportService {
|
||||
private let client: Report_ReportAPIClient
|
||||
final class FogReportConnection:
|
||||
ArbitraryConnection<FogReportGrpcConnection, FogReportHttpConnection>, FogReportService
|
||||
{
|
||||
private let url: FogUrl
|
||||
private let channelManager: GrpcChannelManager
|
||||
private let targetQueue: DispatchQueue?
|
||||
|
||||
init(url: FogUrl, channelManager: GrpcChannelManager, targetQueue: DispatchQueue?) {
|
||||
let channel = channelManager.channel(for: url)
|
||||
self.client = Report_ReportAPIClient(channel: channel)
|
||||
super.init(url: url, targetQueue: targetQueue)
|
||||
init(
|
||||
url: FogUrl,
|
||||
transportProtocolOption: TransportProtocol.Option,
|
||||
channelManager: GrpcChannelManager,
|
||||
targetQueue: DispatchQueue?
|
||||
) {
|
||||
self.url = url
|
||||
self.channelManager = channelManager
|
||||
self.targetQueue = targetQueue
|
||||
|
||||
super.init(
|
||||
connectionOptionWrapperFactory: { transportProtocolOption in
|
||||
switch transportProtocolOption {
|
||||
case .grpc:
|
||||
return .grpc(
|
||||
grpcService: FogReportGrpcConnection(
|
||||
url: url,
|
||||
channelManager: channelManager,
|
||||
targetQueue: targetQueue))
|
||||
case .http:
|
||||
return .http(httpService: FogReportHttpConnection())
|
||||
}
|
||||
},
|
||||
transportProtocolOption: transportProtocolOption,
|
||||
targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func getReports(
|
||||
request: Report_ReportRequest,
|
||||
completion: @escaping (Result<Report_ReportResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
performCall(GetReportsCall(client: client), request: request, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension FogReportConnection {
|
||||
private struct GetReportsCall: GrpcCallable {
|
||||
let client: Report_ReportAPIClient
|
||||
|
||||
func call(
|
||||
request: Report_ReportRequest,
|
||||
callOptions: CallOptions?,
|
||||
completion: @escaping (UnaryCallResult<Report_ReportResponse>) -> Void
|
||||
) {
|
||||
let unaryCall = client.getReports(request, callOptions: callOptions)
|
||||
unaryCall.callResult.whenSuccess(completion)
|
||||
switch connectionOptionWrapper {
|
||||
case .grpc(let grpcConnection):
|
||||
grpcConnection.getReports(request: request, completion: completion)
|
||||
case .http(let httpConnection):
|
||||
httpConnection.getReports(request: request, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,38 +6,49 @@ import Foundation
|
||||
import GRPC
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogUntrustedTxOutConnection: Connection, FogUntrustedTxOutService {
|
||||
private let client: FogLedger_FogUntrustedTxOutApiClient
|
||||
final class FogUntrustedTxOutConnection:
|
||||
Connection<FogUntrustedTxOutGrpcConnection, FogUntrustedTxOutHttpConnection>,
|
||||
FogUntrustedTxOutService
|
||||
{
|
||||
private let config: ConnectionConfig<FogUrl>
|
||||
private let channelManager: GrpcChannelManager
|
||||
private let targetQueue: DispatchQueue?
|
||||
|
||||
init(
|
||||
config: ConnectionConfig<FogUrl>,
|
||||
channelManager: GrpcChannelManager,
|
||||
targetQueue: DispatchQueue?
|
||||
) {
|
||||
let channel = channelManager.channel(for: config)
|
||||
self.client = FogLedger_FogUntrustedTxOutApiClient(channel: channel)
|
||||
super.init(config: config, targetQueue: targetQueue)
|
||||
self.config = config
|
||||
self.channelManager = channelManager
|
||||
self.targetQueue = targetQueue
|
||||
|
||||
super.init(
|
||||
connectionOptionWrapperFactory: { transportProtocolOption in
|
||||
switch transportProtocolOption {
|
||||
case .grpc:
|
||||
return .grpc(
|
||||
grpcService: FogUntrustedTxOutGrpcConnection(
|
||||
config: config,
|
||||
channelManager: channelManager,
|
||||
targetQueue: targetQueue))
|
||||
case .http:
|
||||
return .http(httpService: FogUntrustedTxOutHttpConnection())
|
||||
}
|
||||
},
|
||||
transportProtocolOption: config.transportProtocolOption,
|
||||
targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func getTxOuts(
|
||||
request: FogLedger_TxOutRequest,
|
||||
completion: @escaping (Result<FogLedger_TxOutResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
performCall(GetTxOutsCall(client: client), request: request, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension FogUntrustedTxOutConnection {
|
||||
private struct GetTxOutsCall: GrpcCallable {
|
||||
let client: FogLedger_FogUntrustedTxOutApiClient
|
||||
|
||||
func call(
|
||||
request: FogLedger_TxOutRequest,
|
||||
callOptions: CallOptions?,
|
||||
completion: @escaping (UnaryCallResult<FogLedger_TxOutResponse>) -> Void
|
||||
) {
|
||||
let unaryCall = client.getTxOuts(request, callOptions: callOptions)
|
||||
unaryCall.callResult.whenSuccess(completion)
|
||||
switch connectionOptionWrapper {
|
||||
case .grpc(let grpcConnection):
|
||||
grpcConnection.getTxOuts(request: request, completion: completion)
|
||||
case .http(let httpConnection):
|
||||
httpConnection.getTxOuts(request: request, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,24 +6,54 @@ import Foundation
|
||||
import GRPC
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogViewConnection: AttestedConnection, FogViewService {
|
||||
private let client: FogView_FogViewAPIClient
|
||||
final class FogViewConnection:
|
||||
Connection<FogViewGrpcConnection, FogViewHttpConnection>, FogViewService
|
||||
{
|
||||
private let config: AttestedConnectionConfig<FogUrl>
|
||||
// private let channelManager: GrpcChannelManager
|
||||
private let targetQueue: DispatchQueue?
|
||||
private let rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)?
|
||||
private let rngContext: Any?
|
||||
|
||||
init(
|
||||
config: AttestedConnectionConfig<FogUrl>,
|
||||
channelManager: GrpcChannelManager,
|
||||
httpRequester: HttpRequester?,
|
||||
targetQueue: DispatchQueue?,
|
||||
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
|
||||
rngContext: Any? = nil
|
||||
) {
|
||||
let channel = channelManager.channel(for: config)
|
||||
self.client = FogView_FogViewAPIClient(channel: channel)
|
||||
self.config = config
|
||||
// self.channelManager = channelManager
|
||||
self.targetQueue = targetQueue
|
||||
self.rng = rng
|
||||
self.rngContext = rngContext
|
||||
super.init(
|
||||
client: self.client,
|
||||
config: config,
|
||||
targetQueue: targetQueue,
|
||||
rng: rng,
|
||||
rngContext: rngContext)
|
||||
connectionOptionWrapperFactory: { transportProtocolOption in
|
||||
switch transportProtocolOption {
|
||||
case .grpc:
|
||||
return .grpc(
|
||||
grpcService: FogViewGrpcConnection(
|
||||
config: config,
|
||||
channelManager: channelManager,
|
||||
targetQueue: targetQueue,
|
||||
rng: rng,
|
||||
rngContext: rngContext))
|
||||
case .http:
|
||||
let httpClientWrapper = HttpClientWrapper(
|
||||
config: config,
|
||||
httpRequester: httpRequester)
|
||||
return .http(
|
||||
httpService: FogViewHttpConnection(
|
||||
config: config,
|
||||
client: httpClientWrapper,
|
||||
targetQueue: targetQueue,
|
||||
rng: rng,
|
||||
rngContext: rngContext))
|
||||
}
|
||||
},
|
||||
transportProtocolOption: config.transportProtocolOption,
|
||||
targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func query(
|
||||
@ -31,31 +61,11 @@ final class FogViewConnection: AttestedConnection, FogViewService {
|
||||
request: FogView_QueryRequest,
|
||||
completion: @escaping (Result<FogView_QueryResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
performAttestedCall(
|
||||
EnclaveRequestCall(client: client),
|
||||
requestAad: requestAad,
|
||||
request: request,
|
||||
completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension FogViewConnection {
|
||||
private struct EnclaveRequestCall: AttestedGrpcCallable {
|
||||
typealias InnerRequestAad = FogView_QueryRequestAAD
|
||||
typealias InnerRequest = FogView_QueryRequest
|
||||
typealias InnerResponse = FogView_QueryResponse
|
||||
|
||||
let client: FogView_FogViewAPIClient
|
||||
|
||||
func call(
|
||||
request: Attest_Message,
|
||||
callOptions: CallOptions?,
|
||||
completion: @escaping (UnaryCallResult<Attest_Message>) -> Void
|
||||
) {
|
||||
let unaryCall = client.query(request, callOptions: callOptions)
|
||||
unaryCall.callResult.whenSuccess(completion)
|
||||
switch connectionOptionWrapper {
|
||||
case .grpc(let grpcConnection):
|
||||
grpcConnection.query(requestAad: requestAad, request: request, completion: completion)
|
||||
case .http(let httpConnection):
|
||||
httpConnection.query(requestAad: requestAad, request: request, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension FogView_FogViewAPIClient: AuthGrpcCallableClient {}
|
||||
|
||||
@ -7,17 +7,25 @@ import NIOSSL
|
||||
|
||||
protocol ConnectionConfigProtocol {
|
||||
var url: MobileCoinUrlProtocol { get }
|
||||
var transportProtocolOption: TransportProtocol.Option { get }
|
||||
var trustRoots: [NIOSSLCertificate]? { get }
|
||||
var authorization: BasicCredentials? { get }
|
||||
}
|
||||
|
||||
struct ConnectionConfig<Url: MobileCoinUrlProtocol>: ConnectionConfigProtocol {
|
||||
let urlTyped: Url
|
||||
let transportProtocolOption: TransportProtocol.Option
|
||||
let trustRoots: [NIOSSLCertificate]?
|
||||
let authorization: BasicCredentials?
|
||||
|
||||
init(url: Url, trustRoots: [NIOSSLCertificate]?, authorization: BasicCredentials?) {
|
||||
init(
|
||||
url: Url,
|
||||
transportProtocolOption: TransportProtocol.Option,
|
||||
trustRoots: [NIOSSLCertificate]?,
|
||||
authorization: BasicCredentials?
|
||||
) {
|
||||
self.urlTyped = url
|
||||
self.transportProtocolOption = transportProtocolOption
|
||||
self.trustRoots = trustRoots
|
||||
self.authorization = authorization
|
||||
}
|
||||
|
||||
80
Sources/Network/GrpcConnection/ArbitraryGrpcConnection.swift
Normal file
80
Sources/Network/GrpcConnection/ArbitraryGrpcConnection.swift
Normal file
@ -0,0 +1,80 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import GRPC
|
||||
|
||||
class ArbitraryGrpcConnection {
|
||||
private let inner: SerialDispatchLock<Inner>
|
||||
|
||||
init(url: MobileCoinUrlProtocol, targetQueue: DispatchQueue?) {
|
||||
let inner = Inner(url: url)
|
||||
self.inner = .init(inner, targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func performCall<Call: GrpcCallable>(
|
||||
_ call: Call,
|
||||
request: Call.Request,
|
||||
completion: @escaping (Result<Call.Response, ConnectionError>) -> Void
|
||||
) {
|
||||
func performCallCallback(callResult: UnaryCallResult<Call.Response>) {
|
||||
inner.accessAsync {
|
||||
let result = $0.processResponse(callResult: callResult)
|
||||
switch result {
|
||||
case .success:
|
||||
logger.info("Call complete. url: \($0.url)", logFunction: false)
|
||||
case .failure(let connectionError):
|
||||
let errorMessage =
|
||||
"Connection failure. url: \($0.url), error: \(connectionError)"
|
||||
switch connectionError {
|
||||
case .connectionFailure, .serverRateLimited:
|
||||
logger.warning(errorMessage, logFunction: false)
|
||||
case .authorizationFailure, .invalidServerResponse,
|
||||
.attestationVerificationFailed, .outdatedClient:
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
}
|
||||
}
|
||||
completion(result)
|
||||
}
|
||||
}
|
||||
inner.accessAsync {
|
||||
logger.info("Performing call... url: \($0.url)", logFunction: false)
|
||||
|
||||
let callOptions = $0.requestCallOptions()
|
||||
call.call(request: request, callOptions: callOptions, completion: performCallCallback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ArbitraryGrpcConnection {
|
||||
private struct Inner {
|
||||
let url: MobileCoinUrlProtocol
|
||||
private let session: ConnectionSession
|
||||
|
||||
init(url: MobileCoinUrlProtocol) {
|
||||
self.url = url
|
||||
self.session = ConnectionSession(url: url)
|
||||
}
|
||||
|
||||
func requestCallOptions() -> CallOptions {
|
||||
var callOptions = CallOptions()
|
||||
session.addRequestHeaders(to: &callOptions.customMetadata)
|
||||
return callOptions
|
||||
}
|
||||
|
||||
func processResponse<Response>(callResult: UnaryCallResult<Response>)
|
||||
-> Result<Response, ConnectionError>
|
||||
{
|
||||
guard callResult.status.isOk, let response = callResult.response else {
|
||||
return .failure(.connectionFailure(String(describing: callResult.status)))
|
||||
}
|
||||
|
||||
if let initialMetadata = callResult.initialMetadata {
|
||||
session.processResponse(headers: initialMetadata)
|
||||
}
|
||||
|
||||
return .success(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
364
Sources/Network/GrpcConnection/AttestedGrpcConnection.swift
Normal file
364
Sources/Network/GrpcConnection/AttestedGrpcConnection.swift
Normal file
@ -0,0 +1,364 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable closure_body_length cyclomatic_complexity function_body_length
|
||||
// swiftlint:disable multiline_function_chains operator_usage_whitespace
|
||||
|
||||
import Foundation
|
||||
import GRPC
|
||||
import LibMobileCoin
|
||||
|
||||
enum AttestedGrpcConnectionError: Error {
|
||||
case connectionError(ConnectionError)
|
||||
case attestationFailure(String = String())
|
||||
}
|
||||
|
||||
extension AttestedGrpcConnectionError: CustomStringConvertible {
|
||||
var description: String {
|
||||
"Attested connection error: " + {
|
||||
switch self {
|
||||
case .connectionError(let connectionError):
|
||||
return "\(connectionError)"
|
||||
case .attestationFailure(let reason):
|
||||
return "Attestation failure\(!reason.isEmpty ? ": \(reason)" : "")"
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
class AttestedGrpcConnection: ConnectionProtocol {
|
||||
private let inner: SerialCallbackLock<Inner>
|
||||
|
||||
init(
|
||||
client: AttestableGrpcClient,
|
||||
config: AttestedConnectionConfigProtocol,
|
||||
targetQueue: DispatchQueue?,
|
||||
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
|
||||
rngContext: Any? = nil
|
||||
) {
|
||||
let inner = Inner(client: client, config: config, rng: rng, rngContext: rngContext)
|
||||
self.inner = .init(inner, targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func setAuthorization(credentials: BasicCredentials) {
|
||||
inner.priorityAccessAsync {
|
||||
$0.setAuthorization(credentials: credentials)
|
||||
}
|
||||
}
|
||||
|
||||
func performAttestedCall<Call: AttestedGrpcCallable>(
|
||||
_ call: Call,
|
||||
request: Call.InnerRequest,
|
||||
completion: @escaping (Result<Call.InnerResponse, ConnectionError>) -> Void
|
||||
) where Call.InnerRequestAad == (), Call.InnerResponseAad == () {
|
||||
performAttestedCall(call, requestAad: (), request: request, completion: completion)
|
||||
}
|
||||
|
||||
func performAttestedCall<Call: AttestedGrpcCallable>(
|
||||
_ call: Call,
|
||||
requestAad: Call.InnerRequestAad,
|
||||
request: Call.InnerRequest,
|
||||
completion: @escaping (Result<Call.InnerResponse, ConnectionError>) -> Void
|
||||
) where Call.InnerResponseAad == () {
|
||||
performAttestedCall(call, requestAad: requestAad, request: request) {
|
||||
completion($0.map { $0.response })
|
||||
}
|
||||
}
|
||||
|
||||
func performAttestedCall<Call: AttestedGrpcCallable>(
|
||||
_ call: Call,
|
||||
request: Call.InnerRequest,
|
||||
completion: @escaping (
|
||||
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
|
||||
ConnectionError>
|
||||
) -> Void
|
||||
) where Call.InnerRequestAad == () {
|
||||
performAttestedCall(call, requestAad: (), request: request, completion: completion)
|
||||
}
|
||||
|
||||
func performAttestedCall<Call: AttestedGrpcCallable>(
|
||||
_ call: Call,
|
||||
requestAad: Call.InnerRequestAad,
|
||||
request: Call.InnerRequest,
|
||||
completion: @escaping (
|
||||
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
|
||||
ConnectionError>
|
||||
) -> Void
|
||||
) {
|
||||
inner.accessAsync(block: { inner, callback in
|
||||
inner.performAttestedCallWithAuth(
|
||||
call,
|
||||
requestAad: requestAad,
|
||||
request: request,
|
||||
completion: callback)
|
||||
}, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension AttestedGrpcConnection {
|
||||
// Note: Because `SerialCallbackLock` is being used to wrap `AttestedConnection.Inner`, calls
|
||||
// to `AttestedConnection.Inner` have exclusive access (other calls will be queued up) until the
|
||||
// executing call invokes the completion handler that returns control back to
|
||||
// `AttestedConnection`, at which point the block passed to the async `SerialCallbackLock`
|
||||
// method that invoked the call to inner will complete and the next async `SerialCallbackLock`
|
||||
// access block will execute.
|
||||
//
|
||||
// This means that calls to `AttestedConnection.Inner` can assume thread-safety until the call
|
||||
// invokes the completion handler.
|
||||
private struct Inner {
|
||||
private let url: MobileCoinUrlProtocol
|
||||
private let session: ConnectionSession
|
||||
private let client: AttestableGrpcClient
|
||||
private let attestAke: AttestAke
|
||||
|
||||
private let responderId: String
|
||||
private let attestationVerifier: AttestationVerifier
|
||||
private let rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)?
|
||||
private let rngContext: Any?
|
||||
|
||||
init(
|
||||
client: AttestableGrpcClient,
|
||||
config: AttestedConnectionConfigProtocol,
|
||||
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
|
||||
rngContext: Any? = nil
|
||||
) {
|
||||
self.url = config.url
|
||||
self.session = ConnectionSession(config: config)
|
||||
self.client = client
|
||||
self.attestAke = AttestAke()
|
||||
self.responderId = config.url.responderId
|
||||
self.attestationVerifier = AttestationVerifier(attestation: config.attestation)
|
||||
self.rng = rng
|
||||
self.rngContext = rngContext
|
||||
}
|
||||
|
||||
func setAuthorization(credentials: BasicCredentials) {
|
||||
session.authorizationCredentials = credentials
|
||||
}
|
||||
|
||||
func performAttestedCallWithAuth<Call: AttestedGrpcCallable>(
|
||||
_ call: Call,
|
||||
requestAad: Call.InnerRequestAad,
|
||||
request: Call.InnerRequest,
|
||||
completion: @escaping (
|
||||
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
|
||||
ConnectionError>
|
||||
) -> Void
|
||||
) {
|
||||
doPerformAttestedCallWithAuth(
|
||||
call,
|
||||
requestAad: requestAad,
|
||||
request: request,
|
||||
attestAkeCipher: attestAke.cipher.map { ($0, freshCipher: false) },
|
||||
completion: completion)
|
||||
}
|
||||
|
||||
private func doPerformAttestedCallWithAuth<Call: AttestedGrpcCallable>(
|
||||
_ call: Call,
|
||||
requestAad: Call.InnerRequestAad,
|
||||
request: Call.InnerRequest,
|
||||
attestAkeCipher: (AttestAke.Cipher, freshCipher: Bool)?,
|
||||
completion: @escaping (
|
||||
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
|
||||
ConnectionError>
|
||||
) -> Void
|
||||
) {
|
||||
if let (attestAkeCipher, freshCipher) = attestAkeCipher {
|
||||
logger.info(
|
||||
"Performing attested call... url: \(self.url)",
|
||||
logFunction: false)
|
||||
|
||||
doPerformAttestedCall(
|
||||
call,
|
||||
requestAad: requestAad,
|
||||
request: request,
|
||||
attestAkeCipher: attestAkeCipher
|
||||
) {
|
||||
switch $0 {
|
||||
case .success(let response):
|
||||
logger.info(
|
||||
"Attested call successful. url: \(self.url)",
|
||||
logFunction: false)
|
||||
|
||||
completion(.success(response))
|
||||
case .failure(.connectionError(let connectionError)):
|
||||
let errorMessage = "Connection failure while performing attested call. " +
|
||||
"url: \(self.url), error: \(connectionError)"
|
||||
switch connectionError {
|
||||
case .connectionFailure, .serverRateLimited:
|
||||
logger.warning(errorMessage, logFunction: false)
|
||||
case .authorizationFailure, .invalidServerResponse,
|
||||
.attestationVerificationFailed, .outdatedClient:
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
}
|
||||
|
||||
completion(.failure(connectionError))
|
||||
case .failure(.attestationFailure):
|
||||
self.attestAke.deattest()
|
||||
|
||||
if freshCipher {
|
||||
let errorMessage =
|
||||
"Attestation failure with fresh auth. url: \(self.url)"
|
||||
logger.warning(errorMessage, logFunction: false)
|
||||
|
||||
completion(.failure(.invalidServerResponse(errorMessage)))
|
||||
} else {
|
||||
logger.info(
|
||||
"Attestation failure using cached auth, reattesting... url: " +
|
||||
"\(self.url)",
|
||||
logFunction: false)
|
||||
|
||||
self.doPerformAttestedCallWithAuth(
|
||||
call,
|
||||
requestAad: requestAad,
|
||||
request: request,
|
||||
attestAkeCipher: nil,
|
||||
completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.info(
|
||||
"Peforming attestation... url: \(url)",
|
||||
logFunction: false)
|
||||
|
||||
doPerformAuthCall {
|
||||
switch $0 {
|
||||
case .success(let attestAkeCipher):
|
||||
logger.info(
|
||||
"Attestation successful. url: \(self.url)",
|
||||
logFunction: false)
|
||||
|
||||
self.doPerformAttestedCallWithAuth(
|
||||
call,
|
||||
requestAad: requestAad,
|
||||
request: request,
|
||||
attestAkeCipher: (attestAkeCipher, freshCipher: true),
|
||||
completion: completion)
|
||||
case .failure(let connectionError):
|
||||
let errorMessage = "Connection failure while performing attestation. " +
|
||||
"url: \(self.url), error: \(connectionError)"
|
||||
switch connectionError {
|
||||
case .connectionFailure, .serverRateLimited:
|
||||
logger.warning(errorMessage, logFunction: false)
|
||||
case .authorizationFailure, .invalidServerResponse,
|
||||
.attestationVerificationFailed, .outdatedClient:
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
}
|
||||
|
||||
completion(.failure(connectionError))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func doPerformAuthCall(
|
||||
completion: @escaping (Result<AttestAke.Cipher, ConnectionError>) -> Void
|
||||
) {
|
||||
let request = attestAke.authBeginRequest(
|
||||
responderId: responderId,
|
||||
rng: rng,
|
||||
rngContext: rngContext)
|
||||
|
||||
doPerformCall(
|
||||
AuthGrpcCallableWrapper(authCallable: client.authCallable),
|
||||
request: request
|
||||
) {
|
||||
completion(
|
||||
$0.mapError {
|
||||
switch $0 {
|
||||
case .connectionError(let connectionError):
|
||||
return connectionError
|
||||
case .attestationFailure:
|
||||
self.attestAke.deattest()
|
||||
|
||||
return .invalidServerResponse(
|
||||
"Attestation failure during auth. url: \(self.url)")
|
||||
}
|
||||
}.flatMap { response in
|
||||
self.attestAke.authEnd(
|
||||
authResponse: response,
|
||||
attestationVerifier: self.attestationVerifier
|
||||
).mapError {
|
||||
switch $0 {
|
||||
case .invalidInput(let reason):
|
||||
return .invalidServerResponse(reason)
|
||||
case .attestationVerificationFailed(let reason):
|
||||
return .attestationVerificationFailed(reason)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func doPerformAttestedCall<Call: AttestedGrpcCallable>(
|
||||
_ call: Call,
|
||||
requestAad: Call.InnerRequestAad,
|
||||
request: Call.InnerRequest,
|
||||
attestAkeCipher: AttestAke.Cipher,
|
||||
completion: @escaping (
|
||||
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
|
||||
AttestedGrpcConnectionError>
|
||||
) -> Void
|
||||
) {
|
||||
guard let processedRequest =
|
||||
call.processRequest(
|
||||
requestAad: requestAad,
|
||||
request: request,
|
||||
attestAkeCipher: attestAkeCipher)
|
||||
.mapError({ _ in .attestationFailure() })
|
||||
.successOr(completion: completion)
|
||||
else { return }
|
||||
|
||||
doPerformCall(call, request: processedRequest) {
|
||||
completion($0.flatMap { response in
|
||||
call.processResponse(response: response, attestAkeCipher: attestAkeCipher)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func doPerformCall<Call: GrpcCallable>(
|
||||
_ call: Call,
|
||||
request: Call.Request,
|
||||
completion: @escaping (Result<Call.Response, AttestedGrpcConnectionError>) -> Void
|
||||
) {
|
||||
let callOptions = requestCallOptions()
|
||||
|
||||
call.call(request: request, callOptions: callOptions) {
|
||||
completion(self.processResponse(callResult: $0))
|
||||
}
|
||||
}
|
||||
|
||||
private func requestCallOptions() -> CallOptions {
|
||||
var callOptions = CallOptions()
|
||||
session.addRequestHeaders(to: &callOptions.customMetadata)
|
||||
return callOptions
|
||||
}
|
||||
|
||||
private func processResponse<Response>(callResult: UnaryCallResult<Response>)
|
||||
-> Result<Response, AttestedGrpcConnectionError>
|
||||
{
|
||||
// Basic credential authorization failure
|
||||
guard callResult.status.code != .unauthenticated else {
|
||||
return .failure(.connectionError(.authorizationFailure("url: \(url)")))
|
||||
}
|
||||
|
||||
// Attestation failure, reattest
|
||||
guard callResult.status.code != .permissionDenied else {
|
||||
return .failure(.attestationFailure())
|
||||
}
|
||||
|
||||
guard callResult.status.isOk, let response = callResult.response else {
|
||||
return .failure(.connectionError(
|
||||
.connectionFailure("url: \(url), status: \(callResult.status)")))
|
||||
}
|
||||
|
||||
if let initialMetadata = callResult.initialMetadata {
|
||||
session.processResponse(headers: initialMetadata)
|
||||
}
|
||||
|
||||
return .success(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -39,7 +39,8 @@ protocol AttestedGrpcCallable: GrpcCallable {
|
||||
func processResponse(
|
||||
response: Response,
|
||||
attestAkeCipher: AttestAke.Cipher
|
||||
) -> Result<(responseAad: InnerResponseAad, response: InnerResponse), AttestedConnectionError>
|
||||
) -> Result<(responseAad: InnerResponseAad, response: InnerResponse),
|
||||
AttestedGrpcConnectionError>
|
||||
}
|
||||
|
||||
extension AttestedGrpcCallable where InnerRequestAad == (), InnerRequest == Request {
|
||||
@ -54,7 +55,8 @@ extension AttestedGrpcCallable where InnerRequestAad == (), InnerRequest == Requ
|
||||
|
||||
extension AttestedGrpcCallable where InnerResponseAad == (), InnerResponse == Response {
|
||||
func processResponse(response: Response, attestAkeCipher: AttestAke.Cipher)
|
||||
-> Result<(responseAad: InnerResponseAad, response: InnerResponse), AttestedConnectionError>
|
||||
-> Result<(responseAad: InnerResponseAad, response: InnerResponse),
|
||||
AttestedGrpcConnectionError>
|
||||
{
|
||||
.success((responseAad: (), response: response))
|
||||
}
|
||||
@ -85,7 +87,9 @@ extension AttestedGrpcCallable
|
||||
func processResponse(
|
||||
response: Attest_Message,
|
||||
attestAkeCipher: AttestAke.Cipher
|
||||
) -> Result<(responseAad: InnerResponseAad, response: InnerResponse), AttestedConnectionError> {
|
||||
) -> Result<(responseAad: InnerResponseAad, response: InnerResponse),
|
||||
AttestedGrpcConnectionError>
|
||||
{
|
||||
guard response.aad == Data() else {
|
||||
return .failure(.connectionError(.invalidServerResponse(
|
||||
"\(Self.self) received unexpected aad: " +
|
||||
@ -133,7 +137,9 @@ extension AttestedGrpcCallable
|
||||
func processResponse(
|
||||
response: Attest_Message,
|
||||
attestAkeCipher: AttestAke.Cipher
|
||||
) -> Result<(responseAad: InnerResponseAad, response: InnerResponse), AttestedConnectionError> {
|
||||
) -> Result<(responseAad: InnerResponseAad, response: InnerResponse),
|
||||
AttestedGrpcConnectionError>
|
||||
{
|
||||
guard let responseAad = try? InnerResponseAad(serializedData: response.aad) else {
|
||||
return .failure(.connectionError(.invalidServerResponse(
|
||||
"Failed to deserialized attested message aad into \(InnerResponseAad.self). aad: " +
|
||||
102
Sources/Network/GrpcConnection/GrpcConnection.swift
Normal file
102
Sources/Network/GrpcConnection/GrpcConnection.swift
Normal file
@ -0,0 +1,102 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import GRPC
|
||||
|
||||
class GrpcConnection: ConnectionProtocol {
|
||||
private let inner: SerialDispatchLock<Inner>
|
||||
|
||||
init(config: ConnectionConfigProtocol, targetQueue: DispatchQueue?) {
|
||||
let inner = Inner(config: config)
|
||||
self.inner = .init(inner, targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func setAuthorization(credentials: BasicCredentials) {
|
||||
inner.accessAsync {
|
||||
$0.setAuthorization(credentials: credentials)
|
||||
}
|
||||
}
|
||||
|
||||
func performCall<Call: GrpcCallable>(
|
||||
_ call: Call,
|
||||
request: Call.Request,
|
||||
completion: @escaping (Result<Call.Response, ConnectionError>) -> Void
|
||||
) {
|
||||
func performCallCallback(callResult: UnaryCallResult<Call.Response>) {
|
||||
inner.accessAsync {
|
||||
let result = $0.processResponse(callResult: callResult)
|
||||
switch result {
|
||||
case .success:
|
||||
logger.info("Call complete. url: \($0.url)", logFunction: false)
|
||||
case .failure(let connectionError):
|
||||
let errorMessage =
|
||||
"Connection failure. url: \($0.url), error: \(connectionError)"
|
||||
switch connectionError {
|
||||
case .connectionFailure, .serverRateLimited:
|
||||
logger.warning(errorMessage, logFunction: false)
|
||||
case .authorizationFailure, .invalidServerResponse,
|
||||
.attestationVerificationFailed, .outdatedClient:
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
}
|
||||
}
|
||||
completion(result)
|
||||
}
|
||||
}
|
||||
|
||||
inner.accessAsync {
|
||||
logger.info("Performing call... url: \($0.url)", logFunction: false)
|
||||
|
||||
let callOptions = $0.requestCallOptions()
|
||||
call.call(request: request, callOptions: callOptions, completion: performCallCallback)
|
||||
}
|
||||
}
|
||||
|
||||
func performCall<Call: GrpcCallable>(
|
||||
_ call: Call,
|
||||
completion: @escaping (Result<Call.Response, ConnectionError>) -> Void
|
||||
) where Call.Request == () {
|
||||
performCall(call, request: (), completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension GrpcConnection {
|
||||
private struct Inner {
|
||||
let url: MobileCoinUrlProtocol
|
||||
private let session: ConnectionSession
|
||||
|
||||
init(config: ConnectionConfigProtocol) {
|
||||
self.url = config.url
|
||||
self.session = ConnectionSession(config: config)
|
||||
}
|
||||
|
||||
func setAuthorization(credentials: BasicCredentials) {
|
||||
session.authorizationCredentials = credentials
|
||||
}
|
||||
|
||||
func requestCallOptions() -> CallOptions {
|
||||
var callOptions = CallOptions()
|
||||
session.addRequestHeaders(to: &callOptions.customMetadata)
|
||||
return callOptions
|
||||
}
|
||||
|
||||
func processResponse<Response>(callResult: UnaryCallResult<Response>)
|
||||
-> Result<Response, ConnectionError>
|
||||
{
|
||||
guard callResult.status.code != .unauthenticated else {
|
||||
return .failure(.authorizationFailure("url: \(url)"))
|
||||
}
|
||||
|
||||
guard callResult.status.isOk, let response = callResult.response else {
|
||||
return .failure(.connectionFailure("url: \(url), status: \(callResult.status)"))
|
||||
}
|
||||
|
||||
if let initialMetadata = callResult.initialMetadata {
|
||||
session.processResponse(headers: initialMetadata)
|
||||
}
|
||||
|
||||
return .success(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import GRPC
|
||||
import LibMobileCoin
|
||||
import SwiftProtobuf
|
||||
|
||||
final class BlockchainGrpcConnection: GrpcConnection, BlockchainService {
|
||||
private let client: ConsensusCommon_BlockchainAPIClient
|
||||
|
||||
init(
|
||||
config: ConnectionConfig<ConsensusUrl>,
|
||||
channelManager: GrpcChannelManager,
|
||||
targetQueue: DispatchQueue?
|
||||
) {
|
||||
let channel = channelManager.channel(for: config)
|
||||
self.client = ConsensusCommon_BlockchainAPIClient(channel: channel)
|
||||
super.init(config: config, targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func getLastBlockInfo(
|
||||
completion:
|
||||
@escaping (Result<ConsensusCommon_LastBlockInfoResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
performCall(GetLastBlockInfoCall(client: client), completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension BlockchainGrpcConnection {
|
||||
private struct GetLastBlockInfoCall: GrpcCallable {
|
||||
let client: ConsensusCommon_BlockchainAPIClient
|
||||
|
||||
func call(
|
||||
request: (),
|
||||
callOptions: CallOptions?,
|
||||
completion: @escaping (UnaryCallResult<ConsensusCommon_LastBlockInfoResponse>) -> Void
|
||||
) {
|
||||
let unaryCall =
|
||||
client.getLastBlockInfo(Google_Protobuf_Empty(), callOptions: callOptions)
|
||||
unaryCall.callResult.whenSuccess(completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import GRPC
|
||||
import LibMobileCoin
|
||||
|
||||
final class ConsensusGrpcConnection: AttestedGrpcConnection, ConsensusService {
|
||||
private let client: ConsensusClient_ConsensusClientAPIClient
|
||||
|
||||
init(
|
||||
config: AttestedConnectionConfig<ConsensusUrl>,
|
||||
channelManager: GrpcChannelManager,
|
||||
targetQueue: DispatchQueue?,
|
||||
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
|
||||
rngContext: Any? = nil
|
||||
) {
|
||||
let channel = channelManager.channel(for: config)
|
||||
self.client = ConsensusClient_ConsensusClientAPIClient(channel: channel)
|
||||
super.init(
|
||||
client: Attest_AttestedApiClient(channel: channel),
|
||||
config: config,
|
||||
targetQueue: targetQueue,
|
||||
rng: rng,
|
||||
rngContext: rngContext)
|
||||
}
|
||||
|
||||
func proposeTx(
|
||||
_ tx: External_Tx,
|
||||
completion: @escaping (Result<ConsensusCommon_ProposeTxResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
performAttestedCall(
|
||||
ProposeTxCall(client: client),
|
||||
request: tx,
|
||||
completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension ConsensusGrpcConnection {
|
||||
private struct ProposeTxCall: AttestedGrpcCallable {
|
||||
typealias InnerRequest = External_Tx
|
||||
typealias InnerResponse = ConsensusCommon_ProposeTxResponse
|
||||
|
||||
let client: ConsensusClient_ConsensusClientAPIClient
|
||||
|
||||
func call(
|
||||
request: Attest_Message,
|
||||
callOptions: CallOptions?,
|
||||
completion: @escaping (UnaryCallResult<ConsensusCommon_ProposeTxResponse>) -> Void
|
||||
) {
|
||||
let unaryCall = client.clientTxPropose(request, callOptions: callOptions)
|
||||
unaryCall.callResult.whenSuccess(completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Attest_AttestedApiClient: AuthGrpcCallableClient {}
|
||||
@ -0,0 +1,43 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import GRPC
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogBlockGrpcConnection: GrpcConnection, FogBlockService {
|
||||
private let client: FogLedger_FogBlockAPIClient
|
||||
|
||||
init(
|
||||
config: ConnectionConfig<FogUrl>,
|
||||
channelManager: GrpcChannelManager,
|
||||
targetQueue: DispatchQueue?
|
||||
) {
|
||||
let channel = channelManager.channel(for: config)
|
||||
self.client = FogLedger_FogBlockAPIClient(channel: channel)
|
||||
super.init(config: config, targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func getBlocks(
|
||||
request: FogLedger_BlockRequest,
|
||||
completion: @escaping (Result<FogLedger_BlockResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
performCall(GetBlocksCall(client: client), request: request, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension FogBlockGrpcConnection {
|
||||
private struct GetBlocksCall: GrpcCallable {
|
||||
let client: FogLedger_FogBlockAPIClient
|
||||
|
||||
func call(
|
||||
request: FogLedger_BlockRequest,
|
||||
callOptions: CallOptions?,
|
||||
completion: @escaping (UnaryCallResult<FogLedger_BlockResponse>) -> Void
|
||||
) {
|
||||
let unaryCall = client.getBlocks(request, callOptions: callOptions)
|
||||
unaryCall.callResult.whenSuccess(completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import GRPC
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogKeyImageGrpcConnection: AttestedGrpcConnection, FogKeyImageService {
|
||||
private let client: FogLedger_FogKeyImageAPIClient
|
||||
|
||||
init(
|
||||
config: AttestedConnectionConfig<FogUrl>,
|
||||
channelManager: GrpcChannelManager,
|
||||
targetQueue: DispatchQueue?,
|
||||
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
|
||||
rngContext: Any? = nil
|
||||
) {
|
||||
let channel = channelManager.channel(for: config)
|
||||
self.client = FogLedger_FogKeyImageAPIClient(channel: channel)
|
||||
super.init(
|
||||
client: self.client,
|
||||
config: config,
|
||||
targetQueue: targetQueue,
|
||||
rng: rng,
|
||||
rngContext: rngContext)
|
||||
}
|
||||
|
||||
func checkKeyImages(
|
||||
request: FogLedger_CheckKeyImagesRequest,
|
||||
completion: @escaping (Result<FogLedger_CheckKeyImagesResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
performAttestedCall(
|
||||
CheckKeyImagesCall(client: client),
|
||||
request: request,
|
||||
completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension FogKeyImageGrpcConnection {
|
||||
private struct CheckKeyImagesCall: AttestedGrpcCallable {
|
||||
typealias InnerRequest = FogLedger_CheckKeyImagesRequest
|
||||
typealias InnerResponse = FogLedger_CheckKeyImagesResponse
|
||||
|
||||
let client: FogLedger_FogKeyImageAPIClient
|
||||
|
||||
func call(
|
||||
request: Attest_Message,
|
||||
callOptions: CallOptions?,
|
||||
completion: @escaping (UnaryCallResult<Attest_Message>) -> Void
|
||||
) {
|
||||
let unaryCall = client.checkKeyImages(request, callOptions: callOptions)
|
||||
unaryCall.callResult.whenSuccess(completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension FogLedger_FogKeyImageAPIClient: AuthGrpcCallableClient {}
|
||||
@ -0,0 +1,58 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import GRPC
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogMerkleProofGrpcConnection: AttestedGrpcConnection, FogMerkleProofService {
|
||||
private let client: FogLedger_FogMerkleProofAPIClient
|
||||
|
||||
init(
|
||||
config: AttestedConnectionConfig<FogUrl>,
|
||||
channelManager: GrpcChannelManager,
|
||||
targetQueue: DispatchQueue?,
|
||||
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
|
||||
rngContext: Any? = nil
|
||||
) {
|
||||
let channel = channelManager.channel(for: config)
|
||||
self.client = FogLedger_FogMerkleProofAPIClient(channel: channel)
|
||||
super.init(
|
||||
client: self.client,
|
||||
config: config,
|
||||
targetQueue: targetQueue,
|
||||
rng: rng,
|
||||
rngContext: rngContext)
|
||||
}
|
||||
|
||||
func getOutputs(
|
||||
request: FogLedger_GetOutputsRequest,
|
||||
completion: @escaping (Result<FogLedger_GetOutputsResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
performAttestedCall(
|
||||
GetOutputsCall(client: client),
|
||||
request: request,
|
||||
completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension FogMerkleProofGrpcConnection {
|
||||
private struct GetOutputsCall: AttestedGrpcCallable {
|
||||
typealias InnerRequest = FogLedger_GetOutputsRequest
|
||||
typealias InnerResponse = FogLedger_GetOutputsResponse
|
||||
|
||||
let client: FogLedger_FogMerkleProofAPIClient
|
||||
|
||||
func call(
|
||||
request: Attest_Message,
|
||||
callOptions: CallOptions?,
|
||||
completion: @escaping (UnaryCallResult<Attest_Message>) -> Void
|
||||
) {
|
||||
let unaryCall = client.getOutputs(request, callOptions: callOptions)
|
||||
unaryCall.callResult.whenSuccess(completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension FogLedger_FogMerkleProofAPIClient: AuthGrpcCallableClient {}
|
||||
@ -0,0 +1,39 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import GRPC
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogReportGrpcConnection: ArbitraryGrpcConnection, FogReportService {
|
||||
private let client: Report_ReportAPIClient
|
||||
|
||||
init(url: FogUrl, channelManager: GrpcChannelManager, targetQueue: DispatchQueue?) {
|
||||
let channel = channelManager.channel(for: url)
|
||||
self.client = Report_ReportAPIClient(channel: channel)
|
||||
super.init(url: url, targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func getReports(
|
||||
request: Report_ReportRequest,
|
||||
completion: @escaping (Result<Report_ReportResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
performCall(GetReportsCall(client: client), request: request, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension FogReportGrpcConnection {
|
||||
private struct GetReportsCall: GrpcCallable {
|
||||
let client: Report_ReportAPIClient
|
||||
|
||||
func call(
|
||||
request: Report_ReportRequest,
|
||||
callOptions: CallOptions?,
|
||||
completion: @escaping (UnaryCallResult<Report_ReportResponse>) -> Void
|
||||
) {
|
||||
let unaryCall = client.getReports(request, callOptions: callOptions)
|
||||
unaryCall.callResult.whenSuccess(completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import GRPC
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogUntrustedTxOutGrpcConnection: GrpcConnection, FogUntrustedTxOutService {
|
||||
private let client: FogLedger_FogUntrustedTxOutApiClient
|
||||
|
||||
init(
|
||||
config: ConnectionConfig<FogUrl>,
|
||||
channelManager: GrpcChannelManager,
|
||||
targetQueue: DispatchQueue?
|
||||
) {
|
||||
let channel = channelManager.channel(for: config)
|
||||
self.client = FogLedger_FogUntrustedTxOutApiClient(channel: channel)
|
||||
super.init(config: config, targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func getTxOuts(
|
||||
request: FogLedger_TxOutRequest,
|
||||
completion: @escaping (Result<FogLedger_TxOutResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
performCall(GetTxOutsCall(client: client), request: request, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension FogUntrustedTxOutGrpcConnection {
|
||||
private struct GetTxOutsCall: GrpcCallable {
|
||||
let client: FogLedger_FogUntrustedTxOutApiClient
|
||||
|
||||
func call(
|
||||
request: FogLedger_TxOutRequest,
|
||||
callOptions: CallOptions?,
|
||||
completion: @escaping (UnaryCallResult<FogLedger_TxOutResponse>) -> Void
|
||||
) {
|
||||
let unaryCall = client.getTxOuts(request, callOptions: callOptions)
|
||||
unaryCall.callResult.whenSuccess(completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import GRPC
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogViewGrpcConnection: AttestedGrpcConnection, FogViewService {
|
||||
private let client: FogView_FogViewAPIClient
|
||||
|
||||
init(
|
||||
config: AttestedConnectionConfig<FogUrl>,
|
||||
channelManager: GrpcChannelManager,
|
||||
targetQueue: DispatchQueue?,
|
||||
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
|
||||
rngContext: Any? = nil
|
||||
) {
|
||||
let channel = channelManager.channel(for: config)
|
||||
self.client = FogView_FogViewAPIClient(channel: channel)
|
||||
super.init(
|
||||
client: self.client,
|
||||
config: config,
|
||||
targetQueue: targetQueue,
|
||||
rng: rng,
|
||||
rngContext: rngContext)
|
||||
}
|
||||
|
||||
func query(
|
||||
requestAad: FogView_QueryRequestAAD,
|
||||
request: FogView_QueryRequest,
|
||||
completion: @escaping (Result<FogView_QueryResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
performAttestedCall(
|
||||
EnclaveRequestCall(client: client),
|
||||
requestAad: requestAad,
|
||||
request: request,
|
||||
completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension FogViewGrpcConnection {
|
||||
private struct EnclaveRequestCall: AttestedGrpcCallable {
|
||||
typealias InnerRequestAad = FogView_QueryRequestAAD
|
||||
typealias InnerRequest = FogView_QueryRequest
|
||||
typealias InnerResponse = FogView_QueryResponse
|
||||
|
||||
let client: FogView_FogViewAPIClient
|
||||
|
||||
func call(
|
||||
request: Attest_Message,
|
||||
callOptions: CallOptions?,
|
||||
completion: @escaping (UnaryCallResult<Attest_Message>) -> Void
|
||||
) {
|
||||
let unaryCall = client.query(request, callOptions: callOptions)
|
||||
unaryCall.callResult.whenSuccess(completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension FogView_FogViewAPIClient: AuthGrpcCallableClient {}
|
||||
353
Sources/Network/HttpConnection/AttestedHttpConnection.swift
Normal file
353
Sources/Network/HttpConnection/AttestedHttpConnection.swift
Normal file
@ -0,0 +1,353 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable closure_body_length cyclomatic_complexity function_body_length
|
||||
// swiftlint:disable multiline_function_chains operator_usage_whitespace
|
||||
|
||||
import Foundation
|
||||
import GRPC
|
||||
import LibMobileCoin
|
||||
|
||||
enum AttestedHttpConnectionError: Error {
|
||||
case connectionError(ConnectionError)
|
||||
case attestationFailure(String = String())
|
||||
}
|
||||
|
||||
extension AttestedHttpConnectionError: CustomStringConvertible {
|
||||
var description: String {
|
||||
"Attested connection error: " + {
|
||||
switch self {
|
||||
case .connectionError(let connectionError):
|
||||
return "\(connectionError)"
|
||||
case .attestationFailure(let reason):
|
||||
return "Attestation failure\(!reason.isEmpty ? ": \(reason)" : "")"
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
class AttestedHttpConnection: ConnectionProtocol {
|
||||
private let inner: SerialCallbackLock<Inner>
|
||||
|
||||
init(
|
||||
client: AttestableHttpClient,
|
||||
config: AttestedConnectionConfigProtocol,
|
||||
targetQueue: DispatchQueue?,
|
||||
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
|
||||
rngContext: Any? = nil
|
||||
) {
|
||||
let inner = Inner(client: client, config: config, rng: rng, rngContext: rngContext)
|
||||
self.inner = .init(inner, targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func setAuthorization(credentials: BasicCredentials) {
|
||||
inner.priorityAccessAsync {
|
||||
$0.setAuthorization(credentials: credentials)
|
||||
}
|
||||
}
|
||||
|
||||
func performAttestedCall<Call: AttestedHttpCallable>(
|
||||
_ call: Call,
|
||||
request: Call.InnerRequest,
|
||||
completion: @escaping (Result<Call.InnerResponse, ConnectionError>) -> Void
|
||||
) where Call.InnerRequestAad == (), Call.InnerResponseAad == () {
|
||||
performAttestedCall(call, requestAad: (), request: request, completion: completion)
|
||||
}
|
||||
|
||||
func performAttestedCall<Call: AttestedHttpCallable>(
|
||||
_ call: Call,
|
||||
requestAad: Call.InnerRequestAad,
|
||||
request: Call.InnerRequest,
|
||||
completion: @escaping (Result<Call.InnerResponse, ConnectionError>) -> Void
|
||||
) where Call.InnerResponseAad == () {
|
||||
performAttestedCall(call, requestAad: requestAad, request: request) {
|
||||
completion($0.map { $0.response })
|
||||
}
|
||||
}
|
||||
|
||||
func performAttestedCall<Call: AttestedHttpCallable>(
|
||||
_ call: Call,
|
||||
request: Call.InnerRequest,
|
||||
completion: @escaping (
|
||||
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
|
||||
ConnectionError>
|
||||
) -> Void
|
||||
) where Call.InnerRequestAad == () {
|
||||
performAttestedCall(call, requestAad: (), request: request, completion: completion)
|
||||
}
|
||||
|
||||
func performAttestedCall<Call: AttestedHttpCallable>(
|
||||
_ call: Call,
|
||||
requestAad: Call.InnerRequestAad,
|
||||
request: Call.InnerRequest,
|
||||
completion: @escaping (
|
||||
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
|
||||
ConnectionError>
|
||||
) -> Void
|
||||
) {
|
||||
inner.accessAsync(block: { inner, callback in
|
||||
inner.performAttestedCallWithAuth(
|
||||
call,
|
||||
requestAad: requestAad,
|
||||
request: request,
|
||||
completion: callback)
|
||||
}, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
extension AttestedHttpConnection {
|
||||
// Note: Because `SerialCallbackLock` is being used to wrap `AttestedConnection.Inner`, calls
|
||||
// to `AttestedConnection.Inner` have exclusive access (other calls will be queued up) until the
|
||||
// executing call invokes the completion handler that returns control back to
|
||||
// `AttestedConnection`, at which point the block passed to the async `SerialCallbackLock`
|
||||
// method that invoked the call to inner will complete and the next async `SerialCallbackLock`
|
||||
// access block will execute.
|
||||
//
|
||||
// This means that calls to `AttestedConnection.Inner` can assume thread-safety until the call
|
||||
// invokes the completion handler.
|
||||
private struct Inner {
|
||||
private let url: MobileCoinUrlProtocol
|
||||
private let session: ConnectionSession
|
||||
private let client: AttestableHttpClient
|
||||
private let attestAke: AttestAke
|
||||
|
||||
private let responderId: String
|
||||
private let attestationVerifier: AttestationVerifier
|
||||
private let rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)?
|
||||
private let rngContext: Any?
|
||||
|
||||
init(
|
||||
client: AttestableHttpClient,
|
||||
config: AttestedConnectionConfigProtocol,
|
||||
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
|
||||
rngContext: Any? = nil
|
||||
) {
|
||||
self.url = config.url
|
||||
self.session = ConnectionSession(config: config)
|
||||
self.client = client
|
||||
self.attestAke = AttestAke()
|
||||
self.responderId = config.url.responderId
|
||||
self.attestationVerifier = AttestationVerifier(attestation: config.attestation)
|
||||
self.rng = rng
|
||||
self.rngContext = rngContext
|
||||
}
|
||||
|
||||
func setAuthorization(credentials: BasicCredentials) {
|
||||
session.authorizationCredentials = credentials
|
||||
}
|
||||
|
||||
func performAttestedCallWithAuth<Call: AttestedHttpCallable>(
|
||||
_ call: Call,
|
||||
requestAad: Call.InnerRequestAad,
|
||||
request: Call.InnerRequest,
|
||||
completion: @escaping (
|
||||
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
|
||||
ConnectionError>
|
||||
) -> Void
|
||||
) {
|
||||
doPerformAttestedCallWithAuth(
|
||||
call,
|
||||
requestAad: requestAad,
|
||||
request: request,
|
||||
attestAkeCipher: attestAke.cipher.map { ($0, freshCipher: false) },
|
||||
completion: completion)
|
||||
}
|
||||
|
||||
private func doPerformAttestedCallWithAuth<Call: AttestedHttpCallable>(
|
||||
_ call: Call,
|
||||
requestAad: Call.InnerRequestAad,
|
||||
request: Call.InnerRequest,
|
||||
attestAkeCipher: (AttestAke.Cipher, freshCipher: Bool)?,
|
||||
completion: @escaping (
|
||||
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
|
||||
ConnectionError>
|
||||
) -> Void
|
||||
) {
|
||||
if let (attestAkeCipher, freshCipher) = attestAkeCipher {
|
||||
logger.info(
|
||||
"Performing attested call... url: \(self.url)",
|
||||
logFunction: false)
|
||||
|
||||
doPerformAttestedCall(
|
||||
call,
|
||||
requestAad: requestAad,
|
||||
request: request,
|
||||
attestAkeCipher: attestAkeCipher
|
||||
) {
|
||||
switch $0 {
|
||||
case .success(let response):
|
||||
logger.info(
|
||||
"Attested call successful. url: \(self.url)",
|
||||
logFunction: false)
|
||||
|
||||
completion(.success(response))
|
||||
case .failure(.connectionError(let connectionError)):
|
||||
let errorMessage = "Connection failure while performing attested call. " +
|
||||
"url: \(self.url), error: \(connectionError)"
|
||||
switch connectionError {
|
||||
case .connectionFailure, .serverRateLimited:
|
||||
logger.warning(errorMessage, logFunction: false)
|
||||
case .authorizationFailure, .invalidServerResponse,
|
||||
.attestationVerificationFailed, .outdatedClient:
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
}
|
||||
|
||||
completion(.failure(connectionError))
|
||||
case .failure(.attestationFailure):
|
||||
self.attestAke.deattest()
|
||||
|
||||
if freshCipher {
|
||||
let errorMessage =
|
||||
"Attestation failure with fresh auth. url: \(self.url)"
|
||||
logger.warning(errorMessage, logFunction: false)
|
||||
|
||||
completion(.failure(.invalidServerResponse(errorMessage)))
|
||||
} else {
|
||||
logger.info(
|
||||
"Attestation failure using cached auth, reattesting... url: " +
|
||||
"\(self.url)",
|
||||
logFunction: false)
|
||||
|
||||
self.doPerformAttestedCallWithAuth(
|
||||
call,
|
||||
requestAad: requestAad,
|
||||
request: request,
|
||||
attestAkeCipher: nil,
|
||||
completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.info(
|
||||
"Peforming attestation... url: \(url)",
|
||||
logFunction: false)
|
||||
|
||||
doPerformAuthCall {
|
||||
switch $0 {
|
||||
case .success(let attestAkeCipher):
|
||||
logger.info(
|
||||
"Attestation successful. url: \(self.url)",
|
||||
logFunction: false)
|
||||
|
||||
self.doPerformAttestedCallWithAuth(
|
||||
call,
|
||||
requestAad: requestAad,
|
||||
request: request,
|
||||
attestAkeCipher: (attestAkeCipher, freshCipher: true),
|
||||
completion: completion)
|
||||
case .failure(let connectionError):
|
||||
let errorMessage = "Connection failure while performing attestation. " +
|
||||
"url: \(self.url), error: \(connectionError)"
|
||||
switch connectionError {
|
||||
case .connectionFailure, .serverRateLimited:
|
||||
logger.warning(errorMessage, logFunction: false)
|
||||
case .authorizationFailure, .invalidServerResponse,
|
||||
.attestationVerificationFailed, .outdatedClient:
|
||||
logger.error(errorMessage, logFunction: false)
|
||||
}
|
||||
|
||||
completion(.failure(connectionError))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func doPerformAuthCall(
|
||||
completion: @escaping (Result<AttestAke.Cipher, ConnectionError>) -> Void
|
||||
) {
|
||||
let request = attestAke.authBeginRequest(
|
||||
responderId: responderId,
|
||||
rng: rng,
|
||||
rngContext: rngContext)
|
||||
|
||||
doPerformCall(
|
||||
AuthHttpCallableWrapper(authCallable: client.authCallable),
|
||||
request: request
|
||||
) {
|
||||
completion(
|
||||
$0.mapError {
|
||||
switch $0 {
|
||||
case .connectionError(let connectionError):
|
||||
return connectionError
|
||||
case .attestationFailure:
|
||||
self.attestAke.deattest()
|
||||
|
||||
return .invalidServerResponse(
|
||||
"Attestation failure during auth. url: \(self.url)")
|
||||
}
|
||||
}.flatMap { response in
|
||||
self.attestAke.authEnd(
|
||||
authResponse: response,
|
||||
attestationVerifier: self.attestationVerifier
|
||||
).mapError {
|
||||
switch $0 {
|
||||
case .invalidInput(let reason):
|
||||
return .invalidServerResponse(reason)
|
||||
case .attestationVerificationFailed(let reason):
|
||||
return .attestationVerificationFailed(reason)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func doPerformAttestedCall<Call: AttestedHttpCallable>(
|
||||
_ call: Call,
|
||||
requestAad: Call.InnerRequestAad,
|
||||
request: Call.InnerRequest,
|
||||
attestAkeCipher: AttestAke.Cipher,
|
||||
completion: @escaping (
|
||||
Result<(responseAad: Call.InnerResponseAad, response: Call.InnerResponse),
|
||||
AttestedHttpConnectionError>
|
||||
) -> Void
|
||||
) {
|
||||
guard let processedRequest =
|
||||
call.processRequest(
|
||||
requestAad: requestAad,
|
||||
request: request,
|
||||
attestAkeCipher: attestAkeCipher)
|
||||
.mapError({ _ in .attestationFailure() })
|
||||
.successOr(completion: completion)
|
||||
else { return }
|
||||
|
||||
doPerformCall(call, request: processedRequest) {
|
||||
completion($0.flatMap { response in
|
||||
call.processResponse(response: response, attestAkeCipher: attestAkeCipher)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func doPerformCall<Call: HttpCallable>(
|
||||
_ call: Call,
|
||||
request: Call.Request,
|
||||
completion: @escaping (Result<Call.Response, AttestedHttpConnectionError>) -> Void
|
||||
) {
|
||||
call.call(request: request) {
|
||||
completion(self.processResponse(callResult: $0))
|
||||
}
|
||||
}
|
||||
|
||||
private func processResponse<Response>(callResult: HttpCallResult<Response>)
|
||||
-> Result<Response, AttestedHttpConnectionError>
|
||||
{
|
||||
// Basic credential authorization failure
|
||||
guard callResult.httpResponse.statusCode != 401 else {
|
||||
return .failure(.connectionError(.authorizationFailure("url: \(url)")))
|
||||
}
|
||||
|
||||
// Attestation failure, reattest
|
||||
guard callResult.httpResponse.statusCode != 403 else {
|
||||
return .failure(.attestationFailure())
|
||||
}
|
||||
|
||||
guard callResult.httpResponse.statusCode == 200, let response = callResult.responsePayload else {
|
||||
return .failure(.connectionError(
|
||||
.connectionFailure("url: \(url), status: \(callResult.httpResponse.statusCode)")))
|
||||
}
|
||||
session.processResponse(headers: HPACKHeaders(callResult.headers))
|
||||
|
||||
return .success(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
46
Sources/Network/HttpConnection/HttpClientWrapper.swift
Normal file
46
Sources/Network/HttpConnection/HttpClientWrapper.swift
Normal file
@ -0,0 +1,46 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable multiline_parameters_brackets
|
||||
|
||||
import Foundation
|
||||
|
||||
final class HttpClientWrapper: AttestableHttpClient {
|
||||
|
||||
private let httpRequester: HttpRequester?
|
||||
private let headers: [String: String]
|
||||
private let config: ConnectionConfigProtocol
|
||||
private let httpMethod = HTTPMethod.post
|
||||
var authCallable: AuthHttpCallable { get {
|
||||
AuthHttpCallable
|
||||
} }
|
||||
|
||||
required init(config: ConnectionConfigProtocol, httpRequester: HttpRequester?) {
|
||||
self.httpRequester = httpRequester
|
||||
self.config = config
|
||||
self.headers = [:]
|
||||
}
|
||||
|
||||
func request(
|
||||
body: Data?,
|
||||
completion: @escaping (Result<Data?, Error>) -> Void) {
|
||||
if let requester = httpRequester {
|
||||
requester.request(
|
||||
url: config.url.url,
|
||||
method: httpMethod,
|
||||
headers: headers,
|
||||
body: body) { httpResponse in
|
||||
switch httpResponse {
|
||||
case .success(let response):
|
||||
return completion(.success(response.responseData))
|
||||
case .failure(let error):
|
||||
return completion(.failure(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return completion(
|
||||
.failure(InvalidInputError("HttpRequester was not set in the Network config")))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
final class BlockchainHttpConnection: ConnectionProtocol, BlockchainService {
|
||||
func getLastBlockInfo(
|
||||
completion:
|
||||
@escaping (Result<ConsensusCommon_LastBlockInfoResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
}
|
||||
|
||||
func setAuthorization(credentials: BasicCredentials) {
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
final class ConsensusHttpConnection: ConnectionProtocol, ConsensusService {
|
||||
func proposeTx(
|
||||
_ tx: External_Tx,
|
||||
completion: @escaping (Result<ConsensusCommon_ProposeTxResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
}
|
||||
|
||||
func setAuthorization(credentials: BasicCredentials) {
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogBlockHttpConnection: ConnectionProtocol, FogBlockService {
|
||||
func getBlocks(
|
||||
request: FogLedger_BlockRequest,
|
||||
completion: @escaping (Result<FogLedger_BlockResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
}
|
||||
|
||||
func setAuthorization(credentials: BasicCredentials) {
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogKeyImageHttpConnection: ConnectionProtocol, FogKeyImageService {
|
||||
func checkKeyImages(
|
||||
request: FogLedger_CheckKeyImagesRequest,
|
||||
completion: @escaping (Result<FogLedger_CheckKeyImagesResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
}
|
||||
|
||||
func setAuthorization(credentials: BasicCredentials) {
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogMerkleProofHttpConnection: ConnectionProtocol, FogMerkleProofService {
|
||||
func getOutputs(
|
||||
request: FogLedger_GetOutputsRequest,
|
||||
completion: @escaping (Result<FogLedger_GetOutputsResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
}
|
||||
|
||||
func setAuthorization(credentials: BasicCredentials) {
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogReportHttpConnection: FogReportService {
|
||||
func getReports(
|
||||
request: Report_ReportRequest,
|
||||
completion: @escaping (Result<Report_ReportResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogUntrustedTxOutHttpConnection: ConnectionProtocol, FogUntrustedTxOutService {
|
||||
func getTxOuts(
|
||||
request: FogLedger_TxOutRequest,
|
||||
completion: @escaping (Result<FogLedger_TxOutResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
}
|
||||
|
||||
func setAuthorization(credentials: BasicCredentials) {
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LibMobileCoin
|
||||
|
||||
final class FogViewHttpConnection: AttestedHttpConnection, FogViewService {
|
||||
|
||||
init(
|
||||
config: AttestedConnectionConfig<FogUrl>,
|
||||
client: HttpClientWrapper,
|
||||
targetQueue: DispatchQueue?,
|
||||
rng: (@convention(c) (UnsafeMutableRawPointer?) -> UInt64)? = securityRNG,
|
||||
rngContext: Any? = nil
|
||||
) {
|
||||
super.init(client: client, config: config, targetQueue: targetQueue)
|
||||
}
|
||||
|
||||
func query(
|
||||
requestAad: FogView_QueryRequestAAD,
|
||||
request: FogView_QueryRequest,
|
||||
completion: @escaping (Result<FogView_QueryResponse, ConnectionError>) -> Void
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
43
Sources/Network/HttpConnection/HttpRequester.swift
Normal file
43
Sources/Network/HttpConnection/HttpRequester.swift
Normal file
@ -0,0 +1,43 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
// swiftlint:disable multiline_parameters_brackets
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol HttpRequester {
|
||||
func request(
|
||||
url: URL,
|
||||
method: HTTPMethod,
|
||||
headers: [String: String]?,
|
||||
body: Data?,
|
||||
completion: @escaping (HTTPResult) -> Void)
|
||||
}
|
||||
|
||||
public enum HTTPMethod {
|
||||
case get
|
||||
case post
|
||||
case put
|
||||
case head
|
||||
case patch
|
||||
case delete
|
||||
}
|
||||
|
||||
public struct HTTPResponse {
|
||||
public let httpUrlResponse: HTTPURLResponse
|
||||
public let responseData: Data?
|
||||
|
||||
public var statusCode: Int {
|
||||
httpUrlResponse.statusCode
|
||||
}
|
||||
|
||||
public var allHeaderFields: [AnyHashable: Any] {
|
||||
httpUrlResponse.allHeaderFields
|
||||
}
|
||||
}
|
||||
|
||||
public enum HTTPResult {
|
||||
case success(response: HTTPResponse)
|
||||
case failure(error: Error)
|
||||
}
|
||||
@ -21,12 +21,16 @@ struct NetworkConfig {
|
||||
|
||||
private let attestation: AttestationConfig
|
||||
|
||||
var transportProtocol: TransportProtocol = .grpc
|
||||
|
||||
var consensusTrustRoots: [NIOSSLCertificate]?
|
||||
var fogTrustRoots: [NIOSSLCertificate]?
|
||||
|
||||
var consensusAuthorization: BasicCredentials?
|
||||
var fogUserAuthorization: BasicCredentials?
|
||||
|
||||
var httpRequester: HttpRequester?
|
||||
|
||||
init(consensusUrl: ConsensusUrl, fogUrl: FogUrl, attestation: AttestationConfig) {
|
||||
self.consensusUrl = consensusUrl
|
||||
self.fogUrl = fogUrl
|
||||
@ -36,6 +40,7 @@ struct NetworkConfig {
|
||||
var consensus: AttestedConnectionConfig<ConsensusUrl> {
|
||||
AttestedConnectionConfig(
|
||||
url: consensusUrl,
|
||||
transportProtocolOption: transportProtocol.option,
|
||||
attestation: attestation.consensus,
|
||||
trustRoots: consensusTrustRoots,
|
||||
authorization: consensusAuthorization)
|
||||
@ -44,6 +49,7 @@ struct NetworkConfig {
|
||||
var blockchain: ConnectionConfig<ConsensusUrl> {
|
||||
ConnectionConfig(
|
||||
url: consensusUrl,
|
||||
transportProtocolOption: transportProtocol.option,
|
||||
trustRoots: consensusTrustRoots,
|
||||
authorization: consensusAuthorization)
|
||||
}
|
||||
@ -51,6 +57,7 @@ struct NetworkConfig {
|
||||
var fogView: AttestedConnectionConfig<FogUrl> {
|
||||
AttestedConnectionConfig(
|
||||
url: fogUrl,
|
||||
transportProtocolOption: transportProtocol.option,
|
||||
attestation: attestation.fogView,
|
||||
trustRoots: fogTrustRoots,
|
||||
authorization: fogUserAuthorization)
|
||||
@ -59,6 +66,7 @@ struct NetworkConfig {
|
||||
var fogMerkleProof: AttestedConnectionConfig<FogUrl> {
|
||||
AttestedConnectionConfig(
|
||||
url: fogUrl,
|
||||
transportProtocolOption: transportProtocol.option,
|
||||
attestation: attestation.fogMerkleProof,
|
||||
trustRoots: fogTrustRoots,
|
||||
authorization: fogUserAuthorization)
|
||||
@ -67,6 +75,7 @@ struct NetworkConfig {
|
||||
var fogKeyImage: AttestedConnectionConfig<FogUrl> {
|
||||
AttestedConnectionConfig(
|
||||
url: fogUrl,
|
||||
transportProtocolOption: transportProtocol.option,
|
||||
attestation: attestation.fogKeyImage,
|
||||
trustRoots: fogTrustRoots,
|
||||
authorization: fogUserAuthorization)
|
||||
@ -75,6 +84,7 @@ struct NetworkConfig {
|
||||
var fogBlock: ConnectionConfig<FogUrl> {
|
||||
ConnectionConfig(
|
||||
url: fogUrl,
|
||||
transportProtocolOption: transportProtocol.option,
|
||||
trustRoots: fogTrustRoots,
|
||||
authorization: fogUserAuthorization)
|
||||
}
|
||||
@ -82,6 +92,7 @@ struct NetworkConfig {
|
||||
var fogUntrustedTxOut: ConnectionConfig<FogUrl> {
|
||||
ConnectionConfig(
|
||||
url: fogUrl,
|
||||
transportProtocolOption: transportProtocol.option,
|
||||
trustRoots: fogTrustRoots,
|
||||
authorization: fogUserAuthorization)
|
||||
}
|
||||
|
||||
@ -5,8 +5,7 @@
|
||||
import Foundation
|
||||
|
||||
final class DefaultServiceProvider: ServiceProvider {
|
||||
private let targetQueue: DispatchQueue?
|
||||
private let channelManager = GrpcChannelManager()
|
||||
private let inner: SerialDispatchLock<Inner>
|
||||
|
||||
private let consensus: ConsensusConnection
|
||||
private let blockchain: BlockchainConnection
|
||||
@ -16,10 +15,12 @@ final class DefaultServiceProvider: ServiceProvider {
|
||||
private let block: FogBlockConnection
|
||||
private let untrustedTxOut: FogUntrustedTxOutConnection
|
||||
|
||||
private var reportUrlToReportConnection: [GrpcChannelConfig: FogReportConnection] = [:]
|
||||
|
||||
init(networkConfig: NetworkConfig, targetQueue: DispatchQueue?) {
|
||||
self.targetQueue = targetQueue
|
||||
let channelManager = GrpcChannelManager()
|
||||
|
||||
let inner = Inner(channelManager: channelManager, targetQueue: targetQueue)
|
||||
self.inner = .init(inner, targetQueue: targetQueue)
|
||||
|
||||
self.consensus = ConsensusConnection(
|
||||
config: networkConfig.consensus,
|
||||
channelManager: channelManager,
|
||||
@ -31,6 +32,7 @@ final class DefaultServiceProvider: ServiceProvider {
|
||||
self.view = FogViewConnection(
|
||||
config: networkConfig.fogView,
|
||||
channelManager: channelManager,
|
||||
httpRequester: networkConfig.httpRequester,
|
||||
targetQueue: targetQueue)
|
||||
self.merkleProof = FogMerkleProofConnection(
|
||||
config: networkConfig.fogMerkleProof,
|
||||
@ -56,19 +58,26 @@ final class DefaultServiceProvider: ServiceProvider {
|
||||
var fogMerkleProofService: FogMerkleProofService { merkleProof }
|
||||
var fogKeyImageService: FogKeyImageService { keyImage }
|
||||
var fogBlockService: FogBlockService { block }
|
||||
var fogUntrustedTxOutService: FogUntrustedTxOutConnection { untrustedTxOut }
|
||||
var fogUntrustedTxOutService: FogUntrustedTxOutService { untrustedTxOut }
|
||||
|
||||
func fogReportService(for fogReportUrl: FogUrl) -> FogReportService {
|
||||
let config = GrpcChannelConfig(url: fogReportUrl)
|
||||
guard let reportConnection = reportUrlToReportConnection[config] else {
|
||||
let reportConnection = FogReportConnection(
|
||||
url: fogReportUrl,
|
||||
channelManager: channelManager,
|
||||
targetQueue: targetQueue)
|
||||
reportUrlToReportConnection[config] = reportConnection
|
||||
return reportConnection
|
||||
func fogReportService(
|
||||
for fogReportUrl: FogUrl,
|
||||
completion: @escaping (FogReportService) -> Void
|
||||
) {
|
||||
inner.accessAsync { completion($0.fogReportService(for: fogReportUrl)) }
|
||||
}
|
||||
|
||||
func setTransportProtocolOption(_ transportProtocolOption: TransportProtocol.Option) {
|
||||
inner.accessAsync {
|
||||
$0.setTransportProtocolOption(transportProtocolOption)
|
||||
self.consensus.setTransportProtocolOption(transportProtocolOption)
|
||||
self.blockchain.setTransportProtocolOption(transportProtocolOption)
|
||||
self.view.setTransportProtocolOption(transportProtocolOption)
|
||||
self.merkleProof.setTransportProtocolOption(transportProtocolOption)
|
||||
self.keyImage.setTransportProtocolOption(transportProtocolOption)
|
||||
self.block.setTransportProtocolOption(transportProtocolOption)
|
||||
self.untrustedTxOut.setTransportProtocolOption(transportProtocolOption)
|
||||
}
|
||||
return reportConnection
|
||||
}
|
||||
|
||||
func setConsensusAuthorization(credentials: BasicCredentials) {
|
||||
@ -84,3 +93,42 @@ final class DefaultServiceProvider: ServiceProvider {
|
||||
untrustedTxOut.setAuthorization(credentials: credentials)
|
||||
}
|
||||
}
|
||||
|
||||
extension DefaultServiceProvider {
|
||||
private struct Inner {
|
||||
private let targetQueue: DispatchQueue?
|
||||
private let channelManager: GrpcChannelManager
|
||||
|
||||
private var reportUrlToReportConnection: [GrpcChannelConfig: FogReportConnection] = [:]
|
||||
private(set) var transportProtocolOption: TransportProtocol.Option
|
||||
|
||||
init(channelManager: GrpcChannelManager, targetQueue: DispatchQueue?) {
|
||||
self.targetQueue = targetQueue
|
||||
self.channelManager = channelManager
|
||||
self.transportProtocolOption = TransportProtocol.grpc.option
|
||||
}
|
||||
|
||||
mutating func fogReportService(for fogReportUrl: FogUrl) -> FogReportService {
|
||||
let config = GrpcChannelConfig(url: fogReportUrl)
|
||||
guard let reportConnection = reportUrlToReportConnection[config] else {
|
||||
let reportConnection = FogReportConnection(
|
||||
url: fogReportUrl,
|
||||
transportProtocolOption: transportProtocolOption,
|
||||
channelManager: channelManager,
|
||||
targetQueue: targetQueue)
|
||||
reportUrlToReportConnection[config] = reportConnection
|
||||
return reportConnection
|
||||
}
|
||||
return reportConnection
|
||||
}
|
||||
|
||||
mutating func setTransportProtocolOption(
|
||||
_ transportProtocolOption: TransportProtocol.Option
|
||||
) {
|
||||
self.transportProtocolOption = transportProtocolOption
|
||||
for reportConnection in reportUrlToReportConnection.values {
|
||||
reportConnection.setTransportProtocolOption(transportProtocolOption)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,9 +12,13 @@ protocol ServiceProvider {
|
||||
var fogMerkleProofService: FogMerkleProofService { get }
|
||||
var fogKeyImageService: FogKeyImageService { get }
|
||||
var fogBlockService: FogBlockService { get }
|
||||
var fogUntrustedTxOutService: FogUntrustedTxOutConnection { get }
|
||||
var fogUntrustedTxOutService: FogUntrustedTxOutService { get }
|
||||
|
||||
func fogReportService(for fogReportUrl: FogUrl) -> FogReportService
|
||||
func fogReportService(
|
||||
for fogReportUrl: FogUrl,
|
||||
completion: @escaping (FogReportService) -> Void)
|
||||
|
||||
func setTransportProtocolOption(_ transportProtocolOption: TransportProtocol.Option)
|
||||
|
||||
func setConsensusAuthorization(credentials: BasicCredentials)
|
||||
func setFogUserAuthorization(credentials: BasicCredentials)
|
||||
|
||||
19
Sources/Network/TransportProtocol.swift
Normal file
19
Sources/Network/TransportProtocol.swift
Normal file
@ -0,0 +1,19 @@
|
||||
//
|
||||
// Copyright (c) 2020-2021 MobileCoin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct TransportProtocol {
|
||||
public static let grpc = TransportProtocol(option: .grpc)
|
||||
public static let http = TransportProtocol(option: .http)
|
||||
|
||||
let option: Option
|
||||
}
|
||||
|
||||
extension TransportProtocol {
|
||||
enum Option {
|
||||
case grpc
|
||||
case http
|
||||
}
|
||||
}
|
||||
@ -308,6 +308,32 @@ extension NetworkPreset {
|
||||
|
||||
}
|
||||
|
||||
final class TestHttpRequester: HttpRequester {
|
||||
func request(
|
||||
url: URL,
|
||||
method: HTTPMethod,
|
||||
headers: [String: String]?,
|
||||
body: Data?,
|
||||
completion: @escaping (HTTPResult) -> Void
|
||||
) {
|
||||
let task = URLSession.shared.dataTask(with: url) {data, response, error in
|
||||
if let error = error {
|
||||
completion(.failure(error: error))
|
||||
return
|
||||
}
|
||||
guard let response = response as? HTTPURLResponse,
|
||||
(200...299).contains(response.statusCode) else {
|
||||
completion(.failure(error: ConnectionError.invalidServerResponse("")))
|
||||
return
|
||||
}
|
||||
let httpResponse = HTTPResponse(httpUrlResponse: response, responseData: data)
|
||||
completion(.success(response: httpResponse))
|
||||
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
|
||||
extension NetworkPreset {
|
||||
|
||||
func networkConfig() throws -> NetworkConfig {
|
||||
@ -316,6 +342,7 @@ extension NetworkPreset {
|
||||
consensusUrl: consensusUrl,
|
||||
fogUrl: fogUrl,
|
||||
attestation: attestationConfig).get()
|
||||
networkConfig.httpRequester = TestHttpRequester()
|
||||
networkConfig.consensusTrustRoots = try consensusTrustRoots()
|
||||
networkConfig.fogTrustRoots = try fogTrustRoots()
|
||||
networkConfig.consensusAuthorization = consensusCredentials
|
||||
|
||||
@ -9,7 +9,7 @@ import NIOSSL
|
||||
import XCTest
|
||||
|
||||
enum IntegrationTestFixtures {
|
||||
static let network: NetworkPreset = .alpha
|
||||
static let network: NetworkPreset = .mobiledev
|
||||
}
|
||||
|
||||
extension IntegrationTestFixtures {
|
||||
|
||||
@ -32,6 +32,7 @@ extension FogReportConnectionIntTests {
|
||||
let url = try FogUrl.make(string: IntegrationTestFixtures.network.fogReportUrl).get()
|
||||
return FogReportConnection(
|
||||
url: url,
|
||||
transportProtocolOption: .grpc,
|
||||
channelManager: GrpcChannelManager(),
|
||||
targetQueue: DispatchQueue.main)
|
||||
}
|
||||
|
||||
@ -250,6 +250,7 @@ extension FogViewConnectionIntTests {
|
||||
FogViewConnection(
|
||||
config: networkConfig.fogView,
|
||||
channelManager: GrpcChannelManager(),
|
||||
httpRequester: nil,
|
||||
targetQueue: DispatchQueue.main)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user