Second batch of fixes for the libQuic release crashes

• Fixed a rare crash which could occur when receiving a network response
• Updated to the latest libSession (contains some libQuic fixes)
• Bumped version number
pull/976/head
Morgan Pretty 1 month ago
parent 733694d464
commit 1bc294114b

@ -1 +1 @@
Subproject commit ad21e73a5d17001faff3b30dec7b133ae6c350c0 Subproject commit d113e77c7df369eaa0fcc5dbeb3e1e249efeade9

@ -7977,7 +7977,7 @@
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES; CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CURRENT_PROJECT_VERSION = 444; CURRENT_PROJECT_VERSION = 445;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES; ENABLE_TESTABILITY = YES;
@ -8014,7 +8014,7 @@
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = ""; HEADER_SEARCH_PATHS = "";
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 2.6.0; MARKETING_VERSION = 2.6.1;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = ( OTHER_CFLAGS = (
"-fobjc-arc-exceptions", "-fobjc-arc-exceptions",
@ -8055,7 +8055,7 @@
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES; CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_IDENTITY = "iPhone Distribution";
CURRENT_PROJECT_VERSION = 444; CURRENT_PROJECT_VERSION = 445;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
@ -8087,7 +8087,7 @@
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = ""; HEADER_SEARCH_PATHS = "";
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 2.6.0; MARKETING_VERSION = 2.6.1;
ONLY_ACTIVE_ARCH = NO; ONLY_ACTIVE_ARCH = NO;
OTHER_CFLAGS = ( OTHER_CFLAGS = (
"-DNS_BLOCK_ASSERTIONS=1", "-DNS_BLOCK_ASSERTIONS=1",

@ -20,22 +20,54 @@ public extension LibSession {
static var hasPaths: Bool { !lastPaths.wrappedValue.isEmpty } static var hasPaths: Bool { !lastPaths.wrappedValue.isEmpty }
static var pathsDescription: String { lastPaths.wrappedValue.prettifiedDescription } static var pathsDescription: String { lastPaths.wrappedValue.prettifiedDescription }
typealias NodesCallback = (UnsafeMutablePointer<network_service_node>?, Int) -> Void private class CallbackWrapper<Output> {
typealias NetworkCallback = (Bool, Bool, Int16, Data?) -> Void public let resultPublisher: CurrentValueSubject<Output?, Error> = CurrentValueSubject(nil)
private class CWrapper<Callback> {
let callback: Callback
private var pointersToDeallocate: [UnsafeRawPointer?] = [] private var pointersToDeallocate: [UnsafeRawPointer?] = []
public init(_ callback: Callback) { // MARK: - Initialization
self.callback = callback
deinit {
pointersToDeallocate.forEach { $0?.deallocate() }
} }
public func addUnsafePointerToCleanup<T>(_ pointer: UnsafePointer<T>?) { // MARK: - Functions
pointersToDeallocate.append(UnsafeRawPointer(pointer))
public static func create(_ callback: @escaping (CallbackWrapper<Output>) throws -> Void) -> AnyPublisher<Output, Error> {
let wrapper: CallbackWrapper<Output> = CallbackWrapper()
return Deferred {
Future<Void, Error> { resolver in
do {
try callback(wrapper)
resolver(Result.success(()))
}
catch { resolver(Result.failure(error)) }
}
}
.flatMap { _ -> AnyPublisher<Output, Error> in
wrapper
.resultPublisher
.compactMap { $0 }
.first()
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
} }
deinit { public static func run(_ ctx: UnsafeMutableRawPointer?, _ output: Output) {
pointersToDeallocate.forEach { $0?.deallocate() } guard let ctx: UnsafeMutableRawPointer = ctx else {
return Log.error("[LibSession] CallbackWrapper called with null context.")
}
// Dispatch async so we don't block libSession's internals with Swift logic (which can block other requests)
let wrapper: CallbackWrapper<Output> = Unmanaged<CallbackWrapper<Output>>.fromOpaque(ctx).takeRetainedValue()
DispatchQueue.global(qos: .default).async { [wrapper] in wrapper.resultPublisher.send(output) }
}
public func unsafePointer() -> UnsafeMutableRawPointer { Unmanaged.passRetained(self).toOpaque() }
public func addUnsafePointerToCleanup<T>(_ pointer: UnsafePointer<T>?) {
pointersToDeallocate.append(UnsafeRawPointer(pointer))
} }
} }
@ -95,79 +127,60 @@ public extension LibSession {
} }
static func getSwarm(swarmPublicKey: String) -> AnyPublisher<Set<Snode>, Error> { static func getSwarm(swarmPublicKey: String) -> AnyPublisher<Set<Snode>, Error> {
typealias Output = Result<Set<Snode>, Error>
return getOrCreateNetwork() return getOrCreateNetwork()
.flatMap { network in .flatMap { network in
Deferred { CallbackWrapper<Output>
Future<Set<Snode>, Error> { resolver in .create { wrapper in
let cSwarmPublicKey: [CChar] = swarmPublicKey let cSwarmPublicKey: [CChar] = swarmPublicKey
.suffix(64) // Quick way to drop '05' prefix if present .suffix(64) // Quick way to drop '05' prefix if present
.cArray .cArray
.nullTerminated() .nullTerminated()
let callbackWrapper: CWrapper<NodesCallback> = CWrapper { swarmPtr, swarmSize in
network_get_swarm(network, cSwarmPublicKey, { swarmPtr, swarmSize, ctx in
guard guard
swarmSize > 0, swarmSize > 0,
let cSwarm: UnsafeMutablePointer<network_service_node> = swarmPtr let cSwarm: UnsafeMutablePointer<network_service_node> = swarmPtr
else { else { return CallbackWrapper<Output>.run(ctx, .failure(SnodeAPIError.unableToRetrieveSwarm)) }
// Dispatch async so we don't hold up the libSession thread (which can block other requests)
DispatchQueue.global(qos: .default).async {
resolver(Result.failure(SnodeAPIError.unableToRetrieveSwarm))
}
return
}
var nodes: Set<Snode> = [] var nodes: Set<Snode> = []
(0..<swarmSize).forEach { index in nodes.insert(Snode(cSwarm[index])) } (0..<swarmSize).forEach { index in nodes.insert(Snode(cSwarm[index])) }
CallbackWrapper<Output>.run(ctx, .success(nodes))
// Dispatch async so we don't hold up the libSession thread (which can block other requests) }, wrapper.unsafePointer());
DispatchQueue.global(qos: .default).async {
resolver(Result.success(nodes))
}
}
let cWrapperPtr: UnsafeMutableRawPointer = Unmanaged.passRetained(callbackWrapper).toOpaque()
network_get_swarm(network, cSwarmPublicKey, { swarmPtr, swarmSize, ctx in
Unmanaged<CWrapper<NodesCallback>>.fromOpaque(ctx!).takeRetainedValue()
.callback(swarmPtr, swarmSize)
}, cWrapperPtr);
} }
} .tryMap { result in try result.successOrThrow() }
} }
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
static func getRandomNodes(count: Int) -> AnyPublisher<Set<Snode>, Error> { static func getRandomNodes(count: Int) -> AnyPublisher<Set<Snode>, Error> {
typealias Output = Result<Set<Snode>, Error>
return getOrCreateNetwork() return getOrCreateNetwork()
.flatMap { network in .flatMap { network in
Deferred { CallbackWrapper<Output>
Future<Set<Snode>, Error> { resolver in .create { wrapper in
let callbackWrapper: CWrapper<NodesCallback> = CWrapper { nodesPtr, nodesSize in network_get_random_nodes(network, UInt16(count), { nodesPtr, nodesSize, ctx in
guard guard
nodesSize >= count, nodesSize > 0,
let cSwarm: UnsafeMutablePointer<network_service_node> = nodesPtr let cSwarm: UnsafeMutablePointer<network_service_node> = nodesPtr
else { else { return CallbackWrapper<Output>.run(ctx, .failure(SnodeAPIError.unableToRetrieveSwarm)) }
// Dispatch async so we don't hold up the libSession thread (which can block other requests)
DispatchQueue.global(qos: .default).async {
resolver(Result.failure(SnodeAPIError.unableToRetrieveSwarm))
}
return
}
var nodes: Set<Snode> = [] var nodes: Set<Snode> = []
(0..<nodesSize).forEach { index in nodes.insert(Snode(cSwarm[index])) } (0..<nodesSize).forEach { index in nodes.insert(Snode(cSwarm[index])) }
CallbackWrapper<Output>.run(ctx, .success(nodes))
// Dispatch async so we don't hold up the libSession thread (which can block other requests) }, wrapper.unsafePointer());
DispatchQueue.global(qos: .default).async { }
resolver(Result.success(nodes)) .tryMap { result in
} switch result {
case .failure(let error): throw error
case .success(let nodes):
guard nodes.count > count else { throw SnodeAPIError.unableToRetrieveSwarm }
return nodes
} }
let cWrapperPtr: UnsafeMutableRawPointer = Unmanaged.passRetained(callbackWrapper).toOpaque()
network_get_random_nodes(network, UInt16(count), { nodesPtr, nodesSize, ctx in
Unmanaged<CWrapper<NodesCallback>>.fromOpaque(ctx!).takeRetainedValue()
.callback(nodesPtr, nodesSize)
}, cWrapperPtr);
} }
}
} }
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
@ -179,6 +192,8 @@ public extension LibSession {
timeout: TimeInterval, timeout: TimeInterval,
using dependencies: Dependencies using dependencies: Dependencies
) -> AnyPublisher<(ResponseInfoType, Data?), Error> { ) -> AnyPublisher<(ResponseInfoType, Data?), Error> {
typealias Output = (success: Bool, timeout: Bool, statusCode: Int, data: Data?)
return getOrCreateNetwork() return getOrCreateNetwork()
.tryFlatMap { network in .tryFlatMap { network in
// Prepare the parameters // Prepare the parameters
@ -196,22 +211,8 @@ public extension LibSession {
cPayloadBytes = Array(encodedBody) cPayloadBytes = Array(encodedBody)
} }
return Deferred { return CallbackWrapper<Output>
Future<(ResponseInfoType, Data?), Error> { resolver in .create { wrapper in
let callbackWrapper: CWrapper<NetworkCallback> = CWrapper { success, timeout, statusCode, data in
let maybeError: Error? = processError(success, timeout, statusCode, data, using: dependencies)
// Dispatch async so we don't hold up the libSession thread (which can block other requests)
DispatchQueue.global(qos: .default).async {
switch maybeError {
case .some(let error): resolver(Result.failure(error))
case .none:
resolver(Result.success((Network.ResponseInfo(code: Int(statusCode), headers: [:]), data)))
}
}
}
let cWrapperPtr: UnsafeMutableRawPointer = Unmanaged.passRetained(callbackWrapper).toOpaque()
// Trigger the request // Trigger the request
switch destination { switch destination {
case .snode(let snode): case .snode(let snode):
@ -219,7 +220,7 @@ public extension LibSession {
// Quick way to drop '05' prefix if present // Quick way to drop '05' prefix if present
$0.suffix(64).cString(using: .utf8)?.unsafeCopy() $0.suffix(64).cString(using: .utf8)?.unsafeCopy()
} }
callbackWrapper.addUnsafePointerToCleanup(cSwarmPublicKey) wrapper.addUnsafePointerToCleanup(cSwarmPublicKey)
network_send_onion_request_to_snode_destination( network_send_onion_request_to_snode_destination(
network, network,
@ -230,10 +231,9 @@ public extension LibSession {
Int64(floor(timeout * 1000)), Int64(floor(timeout * 1000)),
{ success, timeout, statusCode, dataPtr, dataLen, ctx in { success, timeout, statusCode, dataPtr, dataLen, ctx in
let data: Data? = dataPtr.map { Data(bytes: $0, count: dataLen) } let data: Data? = dataPtr.map { Data(bytes: $0, count: dataLen) }
Unmanaged<CWrapper<NetworkCallback>>.fromOpaque(ctx!).takeRetainedValue() CallbackWrapper<Output>.run(ctx, (success, timeout, Int(statusCode), data))
.callback(success, timeout, statusCode, data)
}, },
cWrapperPtr wrapper.unsafePointer()
) )
case .server(let method, let scheme, let host, let endpoint, let port, let headers, let x25519PublicKey): case .server(let method, let scheme, let host, let endpoint, let port, let headers, let x25519PublicKey):
@ -256,8 +256,7 @@ public extension LibSession {
else { else {
cHeaderKeysContent.forEach { $0?.deallocate() } cHeaderKeysContent.forEach { $0?.deallocate() }
cHeaderValuesContent.forEach { $0?.deallocate() } cHeaderValuesContent.forEach { $0?.deallocate() }
cWrapperPtr.deallocate() throw LibSessionError.invalidCConversion
return resolver(Result.failure(LibSessionError.invalidCConversion))
} }
// Convert the other types // Convert the other types
@ -295,15 +294,15 @@ public extension LibSession {
) )
// Add a cleanup callback to deallocate the header arrays // Add a cleanup callback to deallocate the header arrays
callbackWrapper.addUnsafePointerToCleanup(cMethod) wrapper.addUnsafePointerToCleanup(cMethod)
callbackWrapper.addUnsafePointerToCleanup(cTargetScheme) wrapper.addUnsafePointerToCleanup(cTargetScheme)
callbackWrapper.addUnsafePointerToCleanup(cHost) wrapper.addUnsafePointerToCleanup(cHost)
callbackWrapper.addUnsafePointerToCleanup(cEndpoint) wrapper.addUnsafePointerToCleanup(cEndpoint)
callbackWrapper.addUnsafePointerToCleanup(cX25519Pubkey) wrapper.addUnsafePointerToCleanup(cX25519Pubkey)
cHeaderKeysContent.forEach { callbackWrapper.addUnsafePointerToCleanup($0) } cHeaderKeysContent.forEach { wrapper.addUnsafePointerToCleanup($0) }
cHeaderValuesContent.forEach { callbackWrapper.addUnsafePointerToCleanup($0) } cHeaderValuesContent.forEach { wrapper.addUnsafePointerToCleanup($0) }
callbackWrapper.addUnsafePointerToCleanup(cHeaderKeys) wrapper.addUnsafePointerToCleanup(cHeaderKeys)
callbackWrapper.addUnsafePointerToCleanup(cHeaderValues) wrapper.addUnsafePointerToCleanup(cHeaderValues)
network_send_onion_request_to_server_destination( network_send_onion_request_to_server_destination(
network, network,
@ -313,14 +312,16 @@ public extension LibSession {
Int64(floor(timeout * 1000)), Int64(floor(timeout * 1000)),
{ success, timeout, statusCode, dataPtr, dataLen, ctx in { success, timeout, statusCode, dataPtr, dataLen, ctx in
let data: Data? = dataPtr.map { Data(bytes: $0, count: dataLen) } let data: Data? = dataPtr.map { Data(bytes: $0, count: dataLen) }
Unmanaged<CWrapper<NetworkCallback>>.fromOpaque(ctx!).takeRetainedValue() CallbackWrapper<Output>.run(ctx, (success, timeout, Int(statusCode), data))
.callback(success, timeout, statusCode, data)
}, },
cWrapperPtr wrapper.unsafePointer()
) )
} }
} }
} .tryMap { success, timeout, statusCode, data -> (any ResponseInfoType, Data?) in
try throwErrorIfNeeded(success, timeout, statusCode, data, using: dependencies)
return (Network.ResponseInfo(code: statusCode), data)
}
} }
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
@ -435,45 +436,39 @@ public extension LibSession {
} }
} }
private static func processError( private static func throwErrorIfNeeded(
_ success: Bool, _ success: Bool,
_ timeout: Bool, _ timeout: Bool,
_ statusCode: Int16, _ statusCode: Int,
_ data: Data?, _ data: Data?,
using dependencies: Dependencies using dependencies: Dependencies
) -> Error? { ) throws {
guard !success || statusCode < 200 || statusCode > 299 else { return nil } guard !success || statusCode < 200 || statusCode > 299 else { return }
guard !timeout else { return NetworkError.timeout } guard !timeout else { throw NetworkError.timeout }
/// Handle status codes with specific meanings /// Handle status codes with specific meanings
switch (statusCode, data.map { String(data: $0, encoding: .ascii) }) { switch (statusCode, data.map { String(data: $0, encoding: .ascii) }) {
case (400, .none): case (400, .none):
return NetworkError.badRequest(error: NetworkError.unknown.errorDescription ?? "Bad Request", rawData: data) throw NetworkError.badRequest(error: NetworkError.unknown.errorDescription ?? "Bad Request", rawData: data)
case (400, .some(let responseString)): return NetworkError.badRequest(error: responseString, rawData: data) case (400, .some(let responseString)): throw NetworkError.badRequest(error: responseString, rawData: data)
case (401, _): case (401, _):
Log.warn("Unauthorised (Failed to verify the signature).") Log.warn("Unauthorised (Failed to verify the signature).")
return NetworkError.unauthorised throw NetworkError.unauthorised
case (404, _): return NetworkError.notFound case (404, _): throw NetworkError.notFound
/// A snode will return a `406` but onion requests v4 seems to return `425` so handle both /// A snode will return a `406` but onion requests v4 seems to return `425` so handle both
case (406, _), (425, _): case (406, _), (425, _):
Log.warn("The user's clock is out of sync with the service node network.") Log.warn("The user's clock is out of sync with the service node network.")
return SnodeAPIError.clockOutOfSync throw SnodeAPIError.clockOutOfSync
case (421, _): return SnodeAPIError.unassociatedPubkey case (421, _): throw SnodeAPIError.unassociatedPubkey
case (429, _): return SnodeAPIError.rateLimited case (429, _): throw SnodeAPIError.rateLimited
case (500, _), (502, _), (503, _): return SnodeAPIError.internalServerError case (500, _), (502, _), (503, _): throw SnodeAPIError.internalServerError
case (_, .none): return NetworkError.unknown case (_, .none): throw NetworkError.unknown
case (_, .some(let responseString)): case (_, .some(let responseString)): throw NetworkError.requestFailed(error: responseString, rawData: data)
// An internal server error could return HTML data, this is an attempt to intercept that case
guard !responseString.starts(with: "500 Internal Server Error") else {
return SnodeAPIError.internalServerError
}
return NetworkError.requestFailed(error: responseString, rawData: data)
} }
} }
} }

@ -12,7 +12,7 @@ public extension Network {
public let code: Int public let code: Int
public let headers: [String: String] public let headers: [String: String]
public init(code: Int, headers: [String: String]) { public init(code: Int, headers: [String: String] = [:]) {
self.code = code self.code = code
self.headers = headers self.headers = headers
} }

Loading…
Cancel
Save