Skip to content

Commit

Permalink
Use the new sendMessage API for mocks
Browse files Browse the repository at this point in the history
  • Loading branch information
kean committed May 21, 2023
1 parent 8bb06e1 commit 366d0ce
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 61 deletions.
2 changes: 1 addition & 1 deletion Sources/Pulse/LoggerStore/LoggerStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1290,7 +1290,7 @@ extension LoggerStore {
extension Version {
static let minimumSupportedVersion = Version(3, 1, 0)
static let currentStoreVersion = Version(3, 6, 0)
static let currentProtocolVersion = Version(4, 0, 0)
static let currentProtocolVersion = Version(4, 0, 1)
}

// MARK: - Constants
Expand Down
46 changes: 39 additions & 7 deletions Sources/Pulse/RemoteLogger/RemoteLogger-Connection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ extension RemoteLogger {
private let connection: NWConnection
private var buffer = Data()
private var id: UInt32 = 0
private var handlers: [UInt32: (Data) -> Void] = [:]
private var handlers: [UInt32: (Data?, Error?) -> Void] = [:]

weak var delegate: RemoteLoggerConnectionDelegate?

Expand Down Expand Up @@ -110,10 +110,10 @@ extension RemoteLogger {
// Otherwise, send it to the delegate as a new message.
if case .packet(let packet) = event,
packet.code == RemoteLogger.PacketCode.message.rawValue,
let id = Message.getID(for: packet.body),
let handler = handlers[id],
let message = try? Message.decode(packet.body) {
handler(message.data)
let header = Message.Header(packet.body),
header.options.contains(.response),
let handler = handlers.removeValue(forKey: header.id) {
handler(try? Message.decode(packet.body).data, nil)
} else {
delegate?.connection(self, didReceiveEvent: event)
}
Expand Down Expand Up @@ -141,8 +141,16 @@ extension RemoteLogger {
}
}

func sendMessage(url: URL, data: Data?, _ completion: ((Data) -> Void)? = nil) {
let message = Message(id: id, url: url, data: data ?? Data())
func sendMessage<T: Encodable>(path: Path, entity: T, _ completion: ((Data?, Error?) -> Void)? = nil) {
do {
sendMessage(path: path, data: try JSONEncoder().encode(entity), completion)
} catch {
pulseLog("Failed to encode a packet: \(error)") // Should never happen
}
}

func sendMessage(path: Path, data: Data? = nil, _ completion: ((Data?, Error?) -> Void)? = nil) {
let message = Message(id: id, options: [], path: path, data: data ?? Data())

if id == UInt32.max {
id = 0
Expand All @@ -151,7 +159,13 @@ extension RemoteLogger {
}

if let completion = completion {
let id = message.id
handlers[message.id] = completion
connection.queue?.asyncAfter(deadline: .now() + .seconds(20)) { [weak self] in
if let handler = self?.handlers.removeValue(forKey: id) {
handler(nil, URLError(.timedOut))
}
}
}

do {
Expand All @@ -162,6 +176,24 @@ extension RemoteLogger {
}
}

func sendResponse<T: Encodable>(for message: Message, entity: T) {
do {
sendResponse(for: message, data: try JSONEncoder().encode(entity))
} catch {
pulseLog("Failed to encode a packet: \(error)") // Should never happen
}
}

func sendResponse(for message: Message, data: Data) {
let message = Message(id: message.id, options: [.response], path: message.path, data: data)
do {
let data = try Message.encode(message)
send(code: .message, data: data)
} catch {
pulseLog("Failed to encode message: \(error)") // Should never happen
}
}

func cancel() {
connection.cancel()
}
Expand Down
107 changes: 61 additions & 46 deletions Sources/Pulse/RemoteLogger/RemoteLogger-Protocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ extension RemoteLogger {
// Moving forward, all non-control packets will be send using this format.
case message = 13
}

struct PacketClientHello: Codable {
let version: String?
let deviceId: UUID
Expand All @@ -47,27 +47,27 @@ extension RemoteLogger {
struct Empty: Codable {
public init() {}
}

struct PacketNetworkMessage {
private struct Manifest: Codable {
let messageSize: UInt32
let requestBodySize: UInt32
let responseBodySize: UInt32

static let size = 12

var totalSize: Int {
Manifest.size + Int(messageSize) + Int(requestBodySize) + Int(responseBodySize)
}
}

static func encode(_ event: LoggerStore.Event.NetworkTaskCompleted) throws -> Data {
var contents = [Data]()

var slimEvent = event
slimEvent.requestBody = nil // Sent separately using binary
slimEvent.responseBody = nil

let messageData = try JSONEncoder().encode(slimEvent)
contents.append(messageData)
if let requestBody = event.requestBody, requestBody.count < Int32.max {
Expand All @@ -76,7 +76,7 @@ extension RemoteLogger {
if let responseBody = event.responseBody, responseBody.count < Int32.max {
contents.append(responseBody)
}

var data = Data()
data.append(Data(UInt32(messageData.count)))
data.append(Data(UInt32(event.requestBody?.count ?? 0)))
Expand All @@ -86,110 +86,125 @@ extension RemoteLogger {
}
return data
}

static func decode(_ data: Data) throws -> LoggerStore.Event.NetworkTaskCompleted {
guard data.count >= Manifest.size else {
throw PacketParsingError.notEnoughData // Should never happen
}

let manifest = Manifest(
messageSize: UInt32(data.from(0, size: 4)),
requestBodySize: UInt32(data.from(4, size: 4)),
responseBodySize: UInt32(data.from(8, size: 4))
)

guard data.count >= manifest.totalSize else {
throw PacketParsingError.notEnoughData // This should never happen
}

let event = try JSONDecoder().decode(
LoggerStore.Event.NetworkTaskCompleted.self,
from: data.from(Manifest.size, size: Int(manifest.messageSize))
)

var requestBody: Data?
if manifest.requestBodySize > 0 {
requestBody = data.from(Manifest.size + Int(manifest.messageSize), size: Int(manifest.requestBodySize))
}

var responseBody: Data?
if manifest.responseBodySize > 0 {
responseBody = data.from(Manifest.size + Int(manifest.messageSize) + Int(manifest.requestBodySize), size: Int(manifest.responseBodySize))
}

return LoggerStore.Event.NetworkTaskCompleted(taskId: event.taskId, taskType: event.taskType, createdAt: event.createdAt, originalRequest: event.originalRequest, currentRequest: event.currentRequest, response: event.response, error: event.error, requestBody: requestBody, responseBody: responseBody, metrics: event.metrics, label: event.label)
}
}

struct Message {
struct Header {
let id: UInt32
let options: Options
let pathSize: UInt32
let dataSize: UInt32

init?(_ data: Data) {
guard data.count >= headerSize else { return nil }
self.id = UInt32(data.from(0, size: 4))
self.options = Options(rawValue: data[4])
self.pathSize = UInt32(data.from(5, size: 4))
self.dataSize = UInt32(data.from(9, size: 4))
}
}

struct Options: OptionSet {
let rawValue: UInt8
init(rawValue: UInt8) { self.rawValue = rawValue }

static let response = Options(rawValue: 1 << 0)
}

let id: UInt32
let url: URL
let options: Options
let path: Path
let data: Data

// - id (UInt32)
// - url size (UInt32)
// - options (UInt8)
// - path size (UInt32)
// - data size (UIInt32)
private static let headerSize = 12

private static let headerSize = 13
static func encode(_ message: Message) throws -> Data {
guard let url = message.url.absoluteString.data(using: .utf8) else {
guard let path = try? JSONEncoder().encode(message.path) else {
throw URLError(.unknown, userInfo: [:]) // Should never happen
}
var data = Data()
// Header
data.append(Data(message.id))
data.append(Data(UInt32(url.count)))
data.append(message.options.rawValue)
data.append(Data(UInt32(path.count)))
data.append(Data(UInt32(message.data.count)))
// URL
data.append(url)
data.append(path)
// Payload
data.append(message.data)
return data
}

static func getID(for data: Data) -> UInt32? {
guard data.count >= 4 else {
return nil
}
return UInt32(data.from(0, size: 4))
}


static func decode(_ data: Data) throws -> Message {
guard data.count >= headerSize else {
guard let header = Header(data) else {
throw PacketParsingError.notEnoughData // Should never happen
}
let id = UInt32(data.from(0, size: 4))
let urlSize = UInt32(data.from(4, size: 4))
let dataSize = UInt32(data.from(8, size: 4))
guard data.count >= (headerSize + Int(urlSize) + Int(dataSize)) else {
guard data.count >= (headerSize + Int(header.pathSize) + Int(header.dataSize)) else {
throw PacketParsingError.notEnoughData // This should never happen
}
guard let urlString = String(data: data.from(headerSize, size: Int(urlSize)), encoding: .utf8),
let url = URL(string: urlString) else {
throw URLError(.badURL, userInfo: [:]) // This should never happen
}
let body = data.from(headerSize + Int(urlSize), size: Int(dataSize))
return Message(id: id, url: url, data: body)
let path = try JSONDecoder().decode(RemoteLogger.Path.self, from: data.from(headerSize, size: Int(header.pathSize)))
let body = data.from(headerSize + Int(header.pathSize), size: Int(header.dataSize))
return Message(id: header.id, options: header.options, path: path, data: body)
}
}

struct GetMockRequest: Codable {
let requestID: UUID
let mockID: UUID
}

struct GetMockResponse: Codable {
let requestID: UUID
let mock: URLSessionMockedResponse
}

enum PacketParsingError: Error {
case notEnoughData
case unsupportedContentSize
}
}

enum Path: Codable {
case updateMocks
case getMockedResponse(mockID: UUID)
}

enum RemoteLoggerAPI {
struct ServerHelloResponse: Codable {
let version: String
}
Expand Down
32 changes: 25 additions & 7 deletions Sources/Pulse/RemoteLogger/RemoteLogger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ public final class RemoteLogger: RemoteLoggerConnectionDelegate {
case .serverHello:
guard connectionState != .connected else { return }
connectionState = .connected
if let response = try? JSONDecoder().decode(RemoteLoggerAPI.ServerHelloResponse.self, from: packet.body) {
if let response = try? JSONDecoder().decode(ServerHelloResponse.self, from: packet.body) {
serverVersion = try Version(string: response.version) // Throw should never happen
} else {
serverVersion = nil
Expand All @@ -330,10 +330,18 @@ public final class RemoteLogger: RemoteLoggerConnectionDelegate {
}
break
case .message:
let _ = try? Message.decode(packet.body)
// TODO: process message
guard let message = try? Message.decode(packet.body) else {
return // New unsupported message
}
switch message.path {
case .updateMocks:
let mocks = try JSONDecoder().decode([URLSessionMock].self, from: message.data)
URLSessionMockManager.shared.update(mocks)
case .getMockedResponse:
break // Server specific (should never happen)
}
default:
assertionFailure("A packet with an invalid code received from the server: \(packet.code.description)")
break // Do nothing
}
}

Expand Down Expand Up @@ -446,9 +454,19 @@ public final class RemoteLogger: RemoteLoggerConnectionDelegate {
guard let connection = connection else {
return completion(nil)
}
let request = GetMockRequest(requestID: UUID(), mockID: mock.mockID)
getMockedResponseCompletions[request.requestID] = completion
connection.send(code: .getMockedResponse, entity: request)
if let version = serverVersion, version >= Version(4, 0, 1) {
connection.sendMessage(path: .getMockedResponse(mockID: mock.mockID)) { data, _ in
if let data = data, let response = try? JSONDecoder().decode(URLSessionMockedResponse.self, from: data) {
completion(response)
} else {
completion(nil)
}
}
} else {
let request = GetMockRequest(requestID: UUID(), mockID: mock.mockID)
getMockedResponseCompletions[request.requestID] = completion
connection.send(code: .getMockedResponse, entity: request)
}
}
}
}
Expand Down

0 comments on commit 366d0ce

Please sign in to comment.