Swift NWConnection with Discord IPC (protocol error) - swift

I try to connect with Discord using IPC, but whatever i try, the error message stays the same Optional("\u{02}\0\0\0(\0\0\0{"code":1003,"message":"protocol error"}")
I started with the encode function, but that is UInt8 because Swift apparently does not support an UInt32 data object.
Then i tried to create an UInt32 (memory) array, to convert that to Data, that failed as well.
can someone point me into the direction where I make a mistake.
import Foundation
import Cocoa
import Network
class Discord {
enum opcode: UInt32 {
case handshake = 0
case frame = 1
case close = 2
case ping = 3
case pong = 4
}
var appID: String
private var connection: NWConnection?
private let endpoint: String = NSTemporaryDirectory() + "discord-ipc-0"
init(appID: String) {
self.appID = appID
}
func connect() {
print("Connecting to \(endpoint)")
connection = NWConnection(
to: NWEndpoint.unix(path: endpoint),
using: .tcp
)
connection?.stateUpdateHandler = { state in
switch state {
case .setup:
print("Setting up...")
case .preparing:
print("Prepairing...")
case .waiting(let error):
print("Waiting: \(error)")
case .ready:
print("Ready...")
case .failed(let error):
print("Failed: \(error)")
case .cancelled:
print("Cancelled :'(")
default:
break
}
}
connection?.receiveMessage { completeContent, contentContext, isComplete, error in
print(
String(data: completeContent ?? Data(), encoding: .utf8),
error
)
}
connection?.start(queue: .global())
}
func uint32encode(opcode: opcode, message string: String) -> Data {
let payload = string.data(using: .utf8)!
var buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: 8 + payload.count, alignment: 0)
defer { buffer.deallocate() }
buffer.copyBytes(from: payload)
buffer[8...] = buffer[..<payload.count]
buffer.storeBytes(of: opcode.rawValue, as: UInt32.self)
buffer.storeBytes(of: UInt32(payload.count), toByteOffset: 4, as: UInt32.self)
let uIntData = Data(bytes: &buffer, count: 8 + payload.count)
return uIntData
}
func encode(opcode: opcode, message string: String) -> Data {
let jsondata = string.data(using: .utf8)!
var data = Data()
data.append(UInt8(opcode.rawValue))
data.append(UInt8(jsondata.count))
data.append(contentsOf: [UInt8](jsondata))
/*
uint32 opcode (0 or 1)
uint32 length (length)
byte[length] jsonData (??)
*/
return data
}
func handshake() {
connect()
// We should say "hello", with opcode handshake
let hello = encode(opcode: .handshake, message: "{\"v\":1,\"client_id\":\"\(appID)\"}")
print("Sending \(String.init(data: hello, encoding: .utf8))")
connection?.send(
content: hello,
completion: .contentProcessed({ error in
print("Error:", error?.localizedDescription)
})
)
}
func handshakev2() {
connect()
// We should say "hello", with opcode handshake
let hello = uint32encode(opcode: .handshake, message: "{\"v\":1,\"client_id\":\"\(appID)\"}")
print("Sending (V2) \(String.init(data: hello, encoding: .utf8))")
connection?.send(
content: hello,
completion: .contentProcessed({ error in
print("Error (V2):", error?.localizedDescription)
})
)
}
}

Related

Swift Test case Falling with Expectation

I am trying to run test case for Failure response . I have an empty json file into project and named it FailureResponse . This file is empty . I trying to count the number of array is empty for example ..
XCTAssertTrue(schools.count==0)
It should pass the test because the json file is empty .
same result fields like school name and School location etc but the problem is it showing error ..
testFailure(): Asynchronous wait failed: Exceeded timeout of 6 seconds, with unfulfilled expectations: "waiting for response".
View Model code...
import Foundation
import Combine
class ViewModel {
private let networkManager = NetworkManager()
#Published private(set) var school = [School]()
func getSchools() {
loadMoreSchools()
}
func loadMoreSchools() {
let newURL = NetworkURLs.baseURL
networkManager
.getModel([School].self, from: newURL) { [weak self] result in
switch result {
case .success(let schoolResponse):
self?.school = schoolResponse
print(schoolResponse)
case .failure(let error):
print(error)
}
}
}
func getSchoolName(by row: Int) -> String {
let schoolName = school[row]
return schoolName.schoolName.uppercased()
}
func getSchoolLocation(by row: Int) -> String {
return "\(school[row].location)"
}
}
Here is my Mock service call ..
class MockService: NetworkManagerProtocol {
var data: Data?
func getModel<Model>(_ type: Model.Type, from url: String, completion: #escaping (Result<Model, Alomafire_Project.NetworkError>) -> ()) where Model : Decodable, Model : Encodable {
if let data = data {
do {
let result = try JSONDecoder().decode(type, from: data)
completion(.success(result))
} catch (let error){
print(error)
}
}
}
}
Here is code for call the local Jason ..
func getData(json: String) throws -> Data {
guard let url = Bundle(for: Alomafire_ProjectTests.self).url(forResource: json, withExtension: "json")
else { return Data() }
return try Data(contentsOf: url)
}
Here is the test case ....
func testFailure() throws {
// Given
mockService.data = try getData(json: "FailureResponse")
var schools: [School] = []
let expectation = expectation(description: "waiting for response")
// When
viewModel?
.$school
.dropFirst()
.sink(receiveValue: { result in
schools = result
expectation.fulfill()
})
.store(in: &subscribers)
// viewModel?.getSchools()
// Then
waitForExpectations(timeout: 10.0)
XCTAssertTrue(schools.count==0)
}
Here is the debug result . it return 0 ..
Here is the screenshot of the result ..
You mention in the question that "the json file is empty." If that is the case, then this test will fail. The MockService assumes that the Data pulled from the json file will be decodable to the type requested. If it isn't the getModel(_:from:completion:) will never call the completion and the test will not complete in the specified time limit. Solve this by calling the completion closure even when the JSONDecoder response with an error.
Also, even if that mock emits the error properly, your ViewModel doesn't do anything with it that would cause the schools type to update.

Swiftui Tasks are not running in Parallel

I have a problem running multiply tasks in parallel in a SwiftUI view.
struct ModelsView: View {
#StateObject var tasks = TasksViewModel()
var body: some View {
NavigationView{
ScrollView {
ForEach(Array(zip(tasks.tasks.indices, tasks.tasks)), id: \.0) { task in
NavigationLink(destination: ModelView()) {
ModelPreviewView(model_name: "3dobject.usdz")
.onAppear {
if task.0 == tasks.tasks.count - 2 {
Task {
print(tasks.tasks.count)
await tasks.fetch_tasks(count: 4)
}
}
}
}
}
}.navigationTitle("3D modelle")
}.onAppear{
Task {
await tasks.fetch_tasks(count: 5)
await tasks.watch_for_new_tasks()
}
}
}
}
In my view, I spawn a task as soon as the View Appears which, first, fetches 5 tasks from the database (this works fine), and then it starts watching for new tasks.
In the Scroll View, right before the bottom is reached, I start loading new tasks. The problem is, the asynchronous function fetch_tasks(count: 4) only gets continued if the asynchronous function watch_for_new_tasks() stops blocking.
actor TasksViewModel: ObservableObject {
#MainActor #Published private(set) var tasks : [Tasks.Task] = []
private var last_fetched_id : String? = nil
func fetch_tasks(count: UInt32) async {
do {
let tasks_data = try await RedisClient.shared.xrevrange(streamName: "tasks", end: last_fetched_id ?? "+" , start: "-", count: count)
last_fetched_id = tasks_data.last?.id
let fetched_tasks = tasks_data.compactMap { Tasks.Task(from: $0.data) }
await MainActor.run {
withAnimation(.easeInOut) {
self.tasks.append(contentsOf: fetched_tasks)
}
}
} catch {
print("Error fetching taskss \(error)")
}
}
func watch_for_new_tasks() async {
while !Task.isCancelled {
do {
let tasks_data = try await RedisClient.shared.xread(streams: "tasks", ids: "$")
let new_tasks = tasks_data.compactMap { Tasks.Task(from: $0.data) }
await MainActor.run {
for new_task in new_tasks.reversed() {
withAnimation {
self.tasks.insert(new_task, at: 0)
}
}
}
} catch {
print(error)
}
}
}
...
}
The asynchronous function watch_for_new_tasks() uses RedisClient.shared.xread(streams: "tasks", ids: "$") which blocks until at least one tasks is added to the Redis Stream.
This is my redis client:
class RedisClient {
typealias Stream = Array<StreamElement>
static let shared = RedisClient(host: "127.0.0.1", port: 6379)
let connection: Redis
let host: String
let port: Int32
init(host: String, port: Int32) {
connection = Redis()
self.host = host
self.port = port
connection.connect(host: host, port: port) {error in
if let err = error {
print(err)
}
}
}
func connect() {
connection.connect(host: self.host, port: self.port) {error in
if let err = error {
print(err)
}
}
}
func xrevrange(streamName: String, end: String, start: String, count: UInt32 = 0) async throws -> Stream {
try await withCheckedThrowingContinuation { continuation in
connection.issueCommand("xrevrange", streamName, end, start, "COUNT", String(count)) { res in
switch res {
case .Array(let data):
continuation.resume(returning: data.compactMap { StreamElement(from: $0) } )
case .Error(let error):
continuation.resume(throwing: ResponseError.RedisError(error))
case _:
continuation.resume(throwing: ResponseError.WrongData("Expected Array"))
}
}
}
}
func xread(streams: String..., ids: String..., block: UInt32 = 0, count: UInt32 = 0) async throws -> Stream {
return try await withCheckedThrowingContinuation({ continuation in
var args = ["xread", "BLOCK", String(block),"COUNT", String(count),"STREAMS"]
args.append(contentsOf: streams)
args.append(contentsOf: ids)
connection.issueCommandInArray(args){ res in
print(res)
switch res.asArray?[safe: 0]?.asArray?[safe: 1] ?? .Error("Expected response to be an array") {
case .Array(let data):
continuation.resume(returning: data.compactMap { StreamElement(from: $0) } )
case .Error(let error):
continuation.resume(throwing: ResponseError.RedisError(error))
case _:
continuation.resume(throwing: ResponseError.WrongData("Expected Array"))
}
}
})
}
func xreadgroup(group: String, consumer: String, count: UInt32 = 0, block: UInt32 = 0, streams: String..., ids: String..., noAck: Bool = true) async throws -> Stream {
try await withCheckedThrowingContinuation({ continuation in
var args = ["xreadgroup", "GROUP", group, consumer, "COUNT", String(count), "BLOCK", noAck ? nil : "NOACK", String(block), "STREAMS"].compactMap{ $0 }
args.append(contentsOf: streams)
args.append(contentsOf: ids)
connection.issueCommandInArray(args){ res in
print(res)
switch res.asArray?[safe: 0]?.asArray?[safe: 1] ?? .Error("Expected response to be an array") {
case .Array(let data):
continuation.resume(returning: data.compactMap { StreamElement(from: $0) } )
case .Error(let error):
continuation.resume(throwing: ResponseError.RedisError(error))
case _:
continuation.resume(throwing: ResponseError.WrongData("Expected Array"))
}
}
})
}
enum ResponseError: Error {
case RedisError(String)
case WrongData(String)
}
struct StreamElement {
let id: String
let data: [RedisResponse]
init?(from value: RedisResponse) {
guard
case .Array(let values) = value,
let id = values[0].asString,
let data = values[1].asArray
else { return nil }
self.id = id.asString
self.data = data
}
}
}
I tried running the watch_for_new_tasks() on a Task.detached tasks, but that also blocks.
To be honest, I have no idea why this blocks, and I could use your guy's help if you could.
Thank you in Advance,
Michael
.onAppear {
Task {
await tasks.fetch_tasks(count: 5)
await tasks.watch_for_new_tasks()
}
}
This does not run tasks in parallel. For the 2nd await to execute, the 1st one has to finish.
You can use .task modifier
You code can be refactored into this to run 2 async functions in parallel:
.task {
async let fetchTask = tasks.fetch_tasks(count: 5)
async let watchTask = tasks.watch_for_new_tasks()
}
You can do:
await [fetchTask, watchTask]
if you need to do something after both of them complete

How to decode the body of an error in Alamofire 5?

I'm trying to migrate my project from Alamofire 4.9 to 5.3 and I'm having a hard time with error handling. I would like to use Decodable as much as possible, but my API endpoints return one JSON structure when everything goes well, and a different JSON structure when there is an error, the same for all errors across all endpoints. The corresponding Codable in my code is ApiError.
I would like to create a custom response serializer that can give me a Result<T, ApiError> instead of the default Result<T, AFError>. I found this article that seems to explain the general process but the code in there does not compile.
How can I create such a custom ResponseSerializer?
I ended up making it work with the following ResponseSerializer:
struct APIError: Error, Decodable {
let message: String
let code: String
let args: [String]
}
final class TwoDecodableResponseSerializer<T: Decodable>: ResponseSerializer {
lazy var decoder: JSONDecoder = {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
return decoder
}()
private lazy var successSerializer = DecodableResponseSerializer<T>(decoder: decoder)
private lazy var errorSerializer = DecodableResponseSerializer<APIError>(decoder: decoder)
public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> Result<T, APIError> {
guard error == nil else { return .failure(APIError(message: "Unknown error", code: "unknown", args: [])) }
guard let response = response else { return .failure(APIError(message: "Empty response", code: "empty_response", args: [])) }
do {
if response.statusCode < 200 || response.statusCode >= 300 {
let result = try errorSerializer.serialize(request: request, response: response, data: data, error: nil)
return .failure(result)
} else {
let result = try successSerializer.serialize(request: request, response: response, data: data, error: nil)
return .success(result)
}
} catch(let err) {
return .failure(APIError(message: "Could not serialize body", code: "unserializable_body", args: [String(data: data!, encoding: .utf8)!, err.localizedDescription]))
}
}
}
extension DataRequest {
#discardableResult func responseTwoDecodable<T: Decodable>(queue: DispatchQueue = DispatchQueue.global(qos: .userInitiated), of t: T.Type, completionHandler: #escaping (Result<T, APIError>) -> Void) -> Self {
return response(queue: .main, responseSerializer: TwoDecodableResponseSerializer<T>()) { response in
switch response.result {
case .success(let result):
completionHandler(result)
case .failure(let error):
completionHandler(.failure(APIError(message: "Other error", code: "other", args: [error.localizedDescription])))
}
}
}
}
And with that, I can call my API like so:
AF.request(request).validate().responseTwoDecodable(of: [Item].self) { response in
switch response {
case .success(let items):
completion(.success(items))
case .failure(let error): //error is an APIError
log.error("Error while loading items: \(String(describing: error))")
completion(.failure(.couldNotLoad(underlyingError: error)))
}
}
I simply consider that any status code outside of the 200-299 range corresponds to an error.
ResponseSerializers have a single requirement. Largely you can just copy the existing serializers. For example, if you wanted to parse a CSV (with no response checking):
struct CommaDelimitedSerializer: ResponseSerializer {
func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> [String] {
// Call the existing StringResponseSerializer to get many behaviors automatically.
let string = try StringResponseSerializer().serialize(request: request,
response: response,
data: data,
error: error)
return Array(string.split(separator: ","))
}
}
You can read more in Alamofire's documentation.

How to convert message received by Swift socket communication to String type

I want to do TCP communication using a library called SwiftSocket.
Below is the sample code of SwiftSocket.
func echoService(client: TCPClient) {
print("Newclient from:\(client.address)[\(client.port)]")
var d = client.read(1024*10)
client.send(data: d!)
client.close()
}
func testServer() {
let server = TCPServer(address: "127.0.0.1", port: 8080)
switch server.listen() {
case .success:
while true {
if var client = server.accept() {
echoService(client: client)
} else {
print("accept error")
}
}
case .failure(let error):
print(error)
}
}
I want to convert the received message to String in the third line of the above code
var d = client.read(1024*10)
How can I do that?
Try this;
if let string = String(bytes: d, encoding: .utf8)
{
print(string)
} else
{
print("not a valid UTF-8 sequence")
}

refine twilio token connection error

At the moment, Users will get a "No internet connection" error, when the cloudfunctions API to get the Twilio token is offline or faulty. This should return a different error message. how do i refine the error message according to the kind of error encountered in swift?
class Token {
let value: String
init (url: String = "userTokenURL") throws {
guard let requestURL = URL(string: url) else {
throw Token.Error.invalidURL
}
do {
let data = try Data(contentsOf: requestURL)
guard let stringToken = String(data: data, encoding: .utf8) else {
throw Token.Error.couldNotConvertDataToString
}
value = stringToken
} catch let error as NSError {
print ("Error fetching token data, \(error)")
throw Token.Error.noInternet
}
}
}
extension Token {
enum Error: Swift.Error {
case invalidURL
case couldNotConvertDataToString
case noInternet
var description: String? {
switch self {
case .invalidURL:
return NSLocalizedString("Token URL is invalid.", comment: "")
case .couldNotConvertDataToString:
return NSLocalizedString("Token Data could not be converted to String.", comment: "")
case .noInternet:
return NSLocalizedString("Internet connection failed.", comment: "")
}
}
}
}