taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

QuickDataTask.swift (8517B)


      1 /*
      2  * This file is part of GNU Taler, ©2022-25 Taler Systems S.A.
      3  * See LICENSE.md
      4  */
      5 /**
      6  * @author Marc Stibane
      7  */
      8 import Foundation
      9 //import "Foundation/NSURLError.h"
     10 import os.log
     11 
     12 import FTalerWalletcore
     13 
     14 // will be called from wallet-core for networking
     15 func request_create(userdata: Optional<UnsafeMutableRawPointer>,
     16                  requestInfo: Optional<UnsafeMutablePointer<JSHttpRequestInfo>>) -> Int32 {
     17     let quickjs = Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue()
     18 
     19     if let requestInfo {
     20         if let url = URL(string: String(cString: requestInfo.pointee.url)) {
     21             let responseCb = requestInfo.pointee.response_cb
     22             let responseCbCls = requestInfo.pointee.response_cb_cls
     23             let method = String(cString: requestInfo.pointee.method)
     24             let requestHeaders = requestInfo.pointee.request_headers
     25                 let redirect = requestInfo.pointee.redirect             // TODO: redirect
     26             let timeoutMs = requestInfo.pointee.timeout_ms
     27                 let debug = requestInfo.pointee.debug                   // TODO: debug
     28             let reqBody = requestInfo.pointee.req_body
     29             let bodyLen = requestInfo.pointee.req_body_len
     30 
     31             var request = URLRequest(url: url)
     32             request.httpMethod = method
     33             request.timeoutInterval = TimeInterval(timeoutMs)
     34             if let reqBody {        // caller will deallocate the req_body after dataTask finish or cancel
     35                 let body = Data(bytesNoCopy: reqBody, count: Int(bodyLen), deallocator: .none)
     36                 request.httpBody = body
     37             }
     38             if var ptr = requestHeaders {
     39                 while let cString = ptr.pointee {
     40                     let string = String(cString: cString)
     41                     if let index = string.firstIndex(of: ":") {
     42                         let headerField = string.prefix(upTo: index)
     43                         let nextIndex = string.index(index, offsetBy: 1)        // skip the ":"
     44                         let value = string.suffix(from: nextIndex)
     45                         request.addValue(String(value), forHTTPHeaderField: String(headerField))
     46                     }
     47                     ptr += 1
     48                 }
     49             }
     50 
     51             return quickjs.reqCreate(request, responseCb, responseCbCls)
     52         }
     53     }
     54     return 0
     55 }
     56 
     57 func request_cancel(userdata: Optional<UnsafeMutableRawPointer>,
     58                    requestID: Int32) -> Int32 {
     59     let quickjs = Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue()
     60     return quickjs.reqCancel(requestID)
     61 }
     62 // MARK: -
     63 extension Error {
     64     var errorCode:Int? {
     65         return (self as NSError).code
     66     }
     67 }
     68 // MARK: -
     69 class QuickDataTask: NSObject {
     70     let urlSession: URLSession
     71     let request: URLRequest
     72     let requestID: Int32
     73     var requests: [Int32: QuickDataTask]
     74     let responseCb: JSHttpResponseCb?
     75     let responseCbCls: Optional<UnsafeMutableRawPointer>
     76 
     77     var dataTask: URLSessionDataTask? = nil
     78     var logger: Logger
     79 
     80     init(urlSession: URLSession,
     81             request: URLRequest,
     82           requestID: Int32,
     83            requests: inout [Int32: QuickDataTask],
     84          responseCb: JSHttpResponseCb?,
     85       responseCbCls: Optional<UnsafeMutableRawPointer>
     86     ) {
     87         self.logger = Logger(subsystem: "net.taler.gnu", category: "Networking")
     88         self.urlSession = urlSession
     89         self.request = request
     90         self.requestID = requestID
     91         self.requests = requests
     92         self.responseCb = responseCb
     93         self.responseCbCls = responseCbCls
     94     }
     95 
     96     func run() {
     97         if let responseCb, let responseCbCls {
     98             let method = self.request.httpMethod ?? "Unknown"
     99             let url = self.request.url?.absoluteString ?? EMPTYSTRING
    100             logger.trace("❓\(self.requestID, privacy: .public)  \(method, privacy: .public) \(url, privacy: .public)")
    101             dataTask = urlSession.dataTask(with: request) { [self] data, response, error in
    102                 let err: Error
    103                 if let response = response as? HTTPURLResponse {
    104                     var headerArray: [String] = []
    105                     var numHeaders: Int32 = 0
    106                     var status = Int32(response.statusCode)
    107                     var errmsg = HTTPURLResponse.localizedString(forStatusCode: Int(status))
    108                     var errmsg_p0 = UnsafeMutablePointer<CChar>(mutating: errmsg.cString(using: .utf8))
    109               // Initialization of 'UnsafeMutablePointer<CChar>' (aka 'UnsafeMutablePointer<Int8>') results in a dangling pointer
    110                     let headers = response.allHeaderFields
    111                     for (key,value) in headers {
    112                         headerArray.append("\(key): \(value)")
    113                         numHeaders += 1
    114                     }
    115                     let cHeaders = CStringArray(headerArray)
    116 
    117                     if let data {
    118                         var ndata:NSData = data as NSData
    119                         var bodyPtr = UnsafeMutableRawPointer(mutating: ndata.bytes)
    120                         var responseInfo = JSHttpResponseInfo(request_id: self.requestID,
    121                                                               status: status,
    122                                                               errmsg: errmsg_p0,
    123                                                     response_headers: cHeaders.pointer,
    124                                                 num_response_headers: numHeaders,
    125                                                                 body: bodyPtr,
    126                                                             body_len: UInt32(data.count))
    127                         let responseInfoPtr = UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo)
    128                         // Initialization of 'UnsafeMutablePointer<JSHttpResponseInfo>' results in a dangling pointer
    129                         logger.trace("❗️ \(self.requestID, privacy: .public) \(url, privacy: .public)")
    130                         responseCb(responseCbCls, responseInfoPtr)
    131                         return
    132                     } else { // data == nil
    133                         logger.error("‼️\(self.requestID, privacy: .public)  \(method, privacy: .public) \(response.statusCode, privacy: .public) \(errmsg, privacy: .public)")
    134                         var responseInfo = JSHttpResponseInfo(request_id: self.requestID,
    135                                                                   status: status,
    136                                                                   errmsg: errmsg_p0,
    137                                                         response_headers: cHeaders.pointer,
    138                                                     num_response_headers: numHeaders,
    139                                                                     body: nil,
    140                                                                 body_len: 0)
    141                         let responseInfoPtr = UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo)
    142                         responseCb(responseCbCls, responseInfoPtr)
    143                     }
    144                 } else {    // pass error to walletCore
    145                     Task.detached {
    146                         self.logger.error("⁉️ \(self.requestID, privacy: .public)  \(method, privacy: .public)  \(error, privacy: .public)")
    147                         Controller.shared.checkInternetConnection()
    148                         let errmsg = error?.localizedDescription
    149                         let errmsg_p0 = if let errmsg { UnsafeMutablePointer<CChar>(mutating: errmsg.cString(using: .utf8)) }
    150                                         else { UnsafeMutablePointer<CChar>(nil) }
    151                         var responseInfo = JSHttpResponseInfo(request_id: self.requestID,
    152                                                                   status: 0,
    153                                                                   errmsg: errmsg_p0,
    154                                                         response_headers: nil,
    155                                                     num_response_headers: 0,
    156                                                                     body: nil,
    157                                                                 body_len: 0)
    158                         let responseInfoPtr = UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo)
    159                         responseCb(responseCbCls, responseInfoPtr)
    160                     }
    161                 }
    162                 requests[requestID] = nil
    163             }
    164             dataTask?.resume()
    165         }
    166     }
    167 }