mirror of https://github.com/oxen-io/session-ios
Merge branch 'charlesmchen/ud2'
commit
12c8eaf060
@ -0,0 +1,33 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public enum OWSSignalAddressError: Error {
|
||||||
|
case assertionError(description: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public class OWSSignalAddress: NSObject {
|
||||||
|
@objc
|
||||||
|
public let recipientId: String
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public let deviceId: UInt
|
||||||
|
|
||||||
|
// MARK: Initializers
|
||||||
|
|
||||||
|
@objc public init(recipientId: String, deviceId: UInt) throws {
|
||||||
|
guard recipientId.count > 0 else {
|
||||||
|
throw OWSSignalAddressError.assertionError(description: "Invalid recipient id: \(deviceId)")
|
||||||
|
}
|
||||||
|
|
||||||
|
guard deviceId > 0 else {
|
||||||
|
throw OWSSignalAddressError.assertionError(description: "Invalid device id: \(deviceId)")
|
||||||
|
}
|
||||||
|
|
||||||
|
self.recipientId = recipientId
|
||||||
|
self.deviceId = deviceId
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,169 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import PromiseKit
|
||||||
|
import SignalMetadataKit
|
||||||
|
import SignalCoreKit
|
||||||
|
|
||||||
|
public enum OWSUDError: Error {
|
||||||
|
case assertionError(description: String)
|
||||||
|
case invalidData(description: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public protocol OWSUDManager: class {
|
||||||
|
|
||||||
|
@objc func setup()
|
||||||
|
|
||||||
|
// MARK: - Recipient state
|
||||||
|
|
||||||
|
@objc func isUDRecipientId(_ recipientId: String) -> Bool
|
||||||
|
|
||||||
|
// No-op if this recipient id is already marked as a "UD recipient".
|
||||||
|
@objc func addUDRecipientId(_ recipientId: String)
|
||||||
|
|
||||||
|
// No-op if this recipient id is already marked as _NOT_ a "UD recipient".
|
||||||
|
@objc func removeUDRecipientId(_ recipientId: String)
|
||||||
|
|
||||||
|
// We use completion handlers instead of a promise so that message sending
|
||||||
|
// logic can access the certificate data.
|
||||||
|
@objc func ensureSenderCertificateObjC(success:@escaping (Data) -> Void,
|
||||||
|
failure:@escaping (Error) -> Void)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: -
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public class OWSUDManagerImpl: NSObject, OWSUDManager {
|
||||||
|
|
||||||
|
private let dbConnection: YapDatabaseConnection
|
||||||
|
|
||||||
|
private let kUDRecipientModeCollection = "kUDRecipientModeCollection"
|
||||||
|
private let kUDCollection = "kUDCollection"
|
||||||
|
private let kUDCurrentSenderCertificateKey = "kUDCurrentSenderCertificateKey"
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public required init(primaryStorage: OWSPrimaryStorage) {
|
||||||
|
self.dbConnection = primaryStorage.newDatabaseConnection()
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
SwiftSingletons.register(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public func setup() {
|
||||||
|
AppReadiness.runNowOrWhenAppIsReady {
|
||||||
|
guard TSAccountManager.isRegistered() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.ensureSenderCertificate().retainUntilComplete()
|
||||||
|
}
|
||||||
|
NotificationCenter.default.addObserver(self,
|
||||||
|
selector: #selector(registrationStateDidChange),
|
||||||
|
name: .RegistrationStateDidChange,
|
||||||
|
object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
func registrationStateDidChange() {
|
||||||
|
AssertIsOnMainThread()
|
||||||
|
|
||||||
|
ensureSenderCertificate().retainUntilComplete()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Recipient state
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public func isUDRecipientId(_ recipientId: String) -> Bool {
|
||||||
|
return dbConnection.bool(forKey: recipientId, inCollection: kUDRecipientModeCollection, defaultValue: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public func addUDRecipientId(_ recipientId: String) {
|
||||||
|
dbConnection.setBool(true, forKey: recipientId, inCollection: kUDRecipientModeCollection)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public func removeUDRecipientId(_ recipientId: String) {
|
||||||
|
dbConnection.removeObject(forKey: recipientId, inCollection: kUDRecipientModeCollection)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Sender Certificate
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
@objc
|
||||||
|
public func hasSenderCertificate() -> Bool {
|
||||||
|
return senderCertificate() != nil
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private func senderCertificate() -> Data? {
|
||||||
|
guard let certificateData = dbConnection.object(forKey: kUDCurrentSenderCertificateKey, inCollection: kUDCollection) as? Data else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard isValidCertificate(certificateData: certificateData) else {
|
||||||
|
Logger.warn("Current sender certificate is not valid.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return certificateData
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setSenderCertificate(_ certificateData: Data) {
|
||||||
|
dbConnection.setObject(certificateData, forKey: kUDCurrentSenderCertificateKey, inCollection: kUDCollection)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public func ensureSenderCertificateObjC(success:@escaping (Data) -> Void,
|
||||||
|
failure:@escaping (Error) -> Void) {
|
||||||
|
ensureSenderCertificate()
|
||||||
|
.then(execute: { certificateData in
|
||||||
|
success(certificateData)
|
||||||
|
})
|
||||||
|
.catch(execute: { (error) in
|
||||||
|
failure(error)
|
||||||
|
}).retainUntilComplete()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func ensureSenderCertificate() -> Promise<Data> {
|
||||||
|
// If there is a valid cached sender certificate, use that.
|
||||||
|
if let certificateData = senderCertificate() {
|
||||||
|
return Promise(value: certificateData)
|
||||||
|
}
|
||||||
|
// Try to obtain a new sender certificate.
|
||||||
|
return requestSenderCertificate().then { (certificateData) in
|
||||||
|
// Cache the current sender certificate.
|
||||||
|
self.setSenderCertificate(certificateData)
|
||||||
|
|
||||||
|
return Promise(value: certificateData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func requestSenderCertificate() -> Promise<Data> {
|
||||||
|
return SignalServiceRestClient().requestUDSenderCertificate().then { (certificateData) in
|
||||||
|
guard self.isValidCertificate(certificateData: certificateData) else {
|
||||||
|
throw OWSUDError.invalidData(description: "Invalid sender certificate returned by server")
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise(value: certificateData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func isValidCertificate(certificateData: Data) -> Bool {
|
||||||
|
do {
|
||||||
|
let certificate = try SMKSenderCertificate.parse(data: certificateData)
|
||||||
|
let expirationMs = certificate.expirationTimestamp
|
||||||
|
let nowMs = NSDate.ows_millisecondTimeStamp()
|
||||||
|
// Ensure that the certificate will not expire in the next hour.
|
||||||
|
// We want a threshold long enough to ensure that any outgoing message
|
||||||
|
// sends will complete before the expiration.
|
||||||
|
let isValid = nowMs + kHourInMs < expirationMs
|
||||||
|
return isValid
|
||||||
|
} catch {
|
||||||
|
OWSLogger.error("Certificate could not be parsed: \(error)")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public class OWSFakeUDManager: NSObject, OWSUDManager {
|
||||||
|
|
||||||
|
@objc public func setup() {}
|
||||||
|
|
||||||
|
// MARK: - Recipient state
|
||||||
|
|
||||||
|
private var udRecipientSet = Set<String>()
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public func isUDRecipientId(_ recipientId: String) -> Bool {
|
||||||
|
return udRecipientSet.contains(recipientId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public func addUDRecipientId(_ recipientId: String) {
|
||||||
|
udRecipientSet.insert(recipientId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public func removeUDRecipientId(_ recipientId: String) {
|
||||||
|
udRecipientSet.remove(recipientId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Server Certificate
|
||||||
|
|
||||||
|
// Tests can control the behavior of this mock by setting this property.
|
||||||
|
@objc public var nextSenderCertificate: Data?
|
||||||
|
|
||||||
|
@objc public func ensureSenderCertificateObjC(success:@escaping (Data) -> Void,
|
||||||
|
failure:@escaping (Error) -> Void) {
|
||||||
|
guard let certificateData = nextSenderCertificate else {
|
||||||
|
failure(OWSUDError.assertionError(description: "No mock server certificate data"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
success(certificateData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,40 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SignalServiceKit
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
class OWSSignalAddressTest: SSKBaseTestSwift {
|
||||||
|
|
||||||
|
override func setUp() {
|
||||||
|
super.setUp()
|
||||||
|
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tearDown() {
|
||||||
|
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||||
|
super.tearDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInitializer() {
|
||||||
|
let recipientId = "+13213214321"
|
||||||
|
let deviceId: UInt = 1
|
||||||
|
let address = try! OWSSignalAddress(recipientId: recipientId, deviceId: deviceId)
|
||||||
|
XCTAssertEqual(address.recipientId, recipientId)
|
||||||
|
XCTAssertEqual(address.deviceId, deviceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInitializer_badRecipientId() {
|
||||||
|
let recipientId = ""
|
||||||
|
let deviceId: UInt = 1
|
||||||
|
XCTAssertThrowsError(try OWSSignalAddress(recipientId: recipientId, deviceId: deviceId))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInitializer_badDeviceId() {
|
||||||
|
let recipientId = "+13213214321"
|
||||||
|
let deviceId: UInt = 0
|
||||||
|
XCTAssertThrowsError(try OWSSignalAddress(recipientId: recipientId, deviceId: deviceId))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
import Foundation
|
||||||
|
import SignalServiceKit
|
||||||
|
|
||||||
|
class OWSUDManagerTest: SSKBaseTestSwift {
|
||||||
|
|
||||||
|
override func setUp() {
|
||||||
|
super.setUp()
|
||||||
|
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tearDown() {
|
||||||
|
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||||
|
super.tearDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInitializer() {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
import SignalServiceKit
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public class SSKBaseTestSwift: XCTestCase {
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public override func setUp() {
|
||||||
|
super.setUp()
|
||||||
|
|
||||||
|
ClearCurrentAppContextForTests()
|
||||||
|
SetCurrentAppContext(TestAppContext())
|
||||||
|
|
||||||
|
MockSSKEnvironment.activate()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public override func tearDown() {
|
||||||
|
super.tearDown()
|
||||||
|
|
||||||
|
SSKEnvironment.shared.primaryStorage.closeForTests()
|
||||||
|
|
||||||
|
ClearCurrentAppContextForTests()
|
||||||
|
SSKEnvironment.clearSharedForTests()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public func read(_ block: @escaping (YapDatabaseReadTransaction) -> Swift.Void) {
|
||||||
|
return OWSPrimaryStorage.shared().dbReadConnection.read(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
public func readWrite(_ block: @escaping (YapDatabaseReadWriteTransaction) -> Swift.Void) {
|
||||||
|
return OWSPrimaryStorage.shared().dbReadWriteConnection.readWrite(block)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue