Skip to content

Commit

Permalink
Merge pull request #68 from trilemma-dev/fatalError-registration
Browse files Browse the repository at this point in the history
Makes repeated route registration result in `fatalError` instead of an `Error`
  • Loading branch information
jakaplan authored Jan 25, 2022
2 parents 38a6b7e + 46f2ab3 commit 888c5a3
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 60 deletions.
91 changes: 48 additions & 43 deletions Sources/SecureXPC/Server/XPCServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,15 @@ import Foundation
/// - ``makeAnonymous()``
/// - ``makeAnonymous(clientRequirements:)``
/// ### Registering Routes
/// - ``registerRoute(_:handler:)-82935``
/// - ``registerRoute(_:handler:)-7yvyr``
/// - ``registerRoute(_:handler:)-3ohmq``
/// - ``registerRoute(_:handler:)-4jjs6``
/// - ``registerRoute(_:handler:)-4ttqe``
/// - ``registerRoute(_:handler:)-9a0x9``
/// - ``registerRoute(_:handler:)-4fxv0``
/// - ``registerRoute(_:handler:)-1jw9d``
/// ### Registering Async Routes
/// - ``registerRoute(_:handler:)-ck6u``
/// - ``registerRoute(_:handler:)-5gscr``
/// - ``registerRoute(_:handler:)-3pv2z``
/// - ``registerRoute(_:handler:)-7l0xv``
/// - ``registerRoute(_:handler:)-6htah``
/// - ``registerRoute(_:handler:)-g7ww``
/// - ``registerRoute(_:handler:)-rw2w``
/// - ``registerRoute(_:handler:)-2vk6u``
/// ### Configuring a Server
/// - ``targetQueue``
/// - ``errorHandler``
Expand Down Expand Up @@ -144,112 +144,117 @@ public class XPCServer {
connections.compactMap{ $0.connection }.forEach{ xpc_connection_set_target_queue($0, newValue) }
}
}

// MARK: Route registration

private func checkUniqueRoute(route: XPCRoute) throws {
if self.routes.keys.contains(route) {
throw XPCError.routeAlreadyRegistered(route.pathComponents)
/// Internal function that actually registers the route and enforces that a route is only ever registered once.
///
/// All of the public functions exist to satisfy type constraints.
private func registerRoute(_ route: XPCRoute, handler: XPCHandler) {
if let _ = self.routes.updateValue(handler, forKey: route) {
fatalError("Route \(route.pathComponents) is already registered")
}
}

/// Registers a route that has no message and can't receive a reply.
///
/// > Important: Routes can only be registered with a handler once; it is a programming error to provide a route which has already been registered.
///
/// - Parameters:
/// - route: A route that has no message and can't receive a reply.
/// - handler: Will be called when the server receives an incoming request for this route if the request is accepted.
/// - Throws: If this route has already been registered.
public func registerRoute(_ route: XPCRouteWithoutMessageWithoutReply,
handler: @escaping () throws -> Void) throws {
try checkUniqueRoute(route: route.route)
self.routes[route.route] = ConstrainedXPCHandlerWithoutMessageWithoutReplySync(handler: handler)
handler: @escaping () throws -> Void) {
self.registerRoute(route.route, handler: ConstrainedXPCHandlerWithoutMessageWithoutReplySync(handler: handler))
}

/// Registers a route that has no message and can't receive a reply.
///
/// > Important: Routes can only be registered with a handler once; it is a programming error to provide a route which has already been registered.
///
/// - Parameters:
/// - route: A route that has no message and can't receive a reply.
/// - handler: Will be called when the server receives an incoming request for this route if the request is accepted.
/// - Throws: If this route has already been registered.
@available(macOS 10.15.0, *)
public func registerRoute(_ route: XPCRouteWithoutMessageWithoutReply,
handler: @escaping () async throws -> Void) throws {
try checkUniqueRoute(route: route.route)
self.routes[route.route] = ConstrainedXPCHandlerWithoutMessageWithoutReplyAsync(handler: handler)
handler: @escaping () async throws -> Void) {
self.registerRoute(route.route, handler: ConstrainedXPCHandlerWithoutMessageWithoutReplyAsync(handler: handler))
}

/// Registers a route that has a message and can't receive a reply.
///
/// > Important: Routes can only be registered with a handler once; it is a programming error to provide a route which has already been registered.
///
/// - Parameters:
/// - route: A route that has a message and can't receive a reply.
/// - handler: Will be called when the server receives an incoming request for this route if the request is accepted.
/// - Throws: If this route has already been registered.
public func registerRoute<M: Decodable>(_ route: XPCRouteWithMessageWithoutReply<M>,
handler: @escaping (M) throws -> Void) throws {
try checkUniqueRoute(route: route.route)
self.routes[route.route] = ConstrainedXPCHandlerWithMessageWithoutReplySync(handler: handler)
handler: @escaping (M) throws -> Void) {
self.registerRoute(route.route, handler: ConstrainedXPCHandlerWithMessageWithoutReplySync(handler: handler))
}

/// Registers a route that has a message and can't receive a reply.
///
/// > Important: Routes can only be registered with a handler once; it is a programming error to provide a route which has already been registered.
///
/// - Parameters:
/// - route: A route that has a message and can't receive a reply.
/// - handler: Will be called when the server receives an incoming request for this route if the request is accepted.
/// - Throws: If this route has already been registered.
@available(macOS 10.15.0, *)
public func registerRoute<M: Decodable>(_ route: XPCRouteWithMessageWithoutReply<M>,
handler: @escaping (M) async throws -> Void) throws {
try checkUniqueRoute(route: route.route)
self.routes[route.route] = ConstrainedXPCHandlerWithMessageWithoutReplyAsync(handler: handler)
handler: @escaping (M) async throws -> Void) {
self.registerRoute(route.route, handler: ConstrainedXPCHandlerWithMessageWithoutReplyAsync(handler: handler))
}

/// Registers a route that has no message and expects a reply.
///
/// > Important: Routes can only be registered with a handler once; it is a programming error to provide a route which has already been registered.
///
/// - Parameters:
/// - route: A route that has no message and expects a reply.
/// - handler: Will be called when the server receives an incoming request for this route if the request is accepted.
/// - Throws: If this route has already been registered.
public func registerRoute<R: Decodable>(_ route: XPCRouteWithoutMessageWithReply<R>,
handler: @escaping () throws -> R) throws {
try checkUniqueRoute(route: route.route)
self.routes[route.route] = ConstrainedXPCHandlerWithoutMessageWithReplySync(handler: handler)
handler: @escaping () throws -> R) {
self.registerRoute(route.route, handler: ConstrainedXPCHandlerWithoutMessageWithReplySync(handler: handler))
}

/// Registers a route that has no message and expects a reply.
///
/// > Important: Routes can only be registered with a handler once; it is a programming error to provide a route which has already been registered.
///
/// - Parameters:
/// - route: A route that has no message and expects a reply.
/// - handler: Will be called when the server receives an incoming request for this route if the request is accepted.
/// - Throws: If this route has already been registered.
@available(macOS 10.15.0, *)
public func registerRoute<R: Decodable>(_ route: XPCRouteWithoutMessageWithReply<R>,
handler: @escaping () async throws -> R) throws {
try checkUniqueRoute(route: route.route)
self.routes[route.route] = ConstrainedXPCHandlerWithoutMessageWithReplyAsync(handler: handler)
handler: @escaping () async throws -> R) {
self.registerRoute(route.route, handler: ConstrainedXPCHandlerWithoutMessageWithReplyAsync(handler: handler))
}

/// Registers a route that has a message and expects a reply.
///
/// > Important: Routes can only be registered with a handler once; it is a programming error to provide a route which has already been registered.
///
/// - Parameters:
/// - route: A route that has a message and expects a reply.
/// - handler: Will be called when the server receives an incoming request for this route if the request is accepted.
/// - Throws: If this route has already been registered.
public func registerRoute<M: Decodable, R: Encodable>(_ route: XPCRouteWithMessageWithReply<M, R>,
handler: @escaping (M) throws -> R) throws {
try checkUniqueRoute(route: route.route)
self.routes[route.route] = ConstrainedXPCHandlerWithMessageWithReplySync(handler: handler)
handler: @escaping (M) throws -> R) {
self.registerRoute(route.route, handler: ConstrainedXPCHandlerWithMessageWithReplySync(handler: handler))
}

/// Registers a route that has a message and expects a reply.
///
/// > Important: Routes can only be registered with a handler once; it is a programming error to provide a route which has already been registered.
///
/// - Parameters:
/// - route: A route that has a message and expects a reply.
/// - handler: Will be called when the server receives an incoming request for this route if the request is accepted.
/// - Throws: If this route has already been registered.
@available(macOS 10.15.0, *)
public func registerRoute<M: Decodable, R: Encodable>(_ route: XPCRouteWithMessageWithReply<M, R>,
handler: @escaping (M) async throws -> R) throws {
try checkUniqueRoute(route: route.route)
self.routes[route.route] = ConstrainedXPCHandlerWithMessageWithReplyAsync(handler: handler)
handler: @escaping (M) async throws -> R) {
self.registerRoute(route.route, handler: ConstrainedXPCHandlerWithMessageWithReplyAsync(handler: handler))
}

internal func startClientConnection(_ connection: xpc_connection_t) {
Expand Down Expand Up @@ -344,7 +349,7 @@ public class XPCServer {
}
} else {
fatalError("Non-sync handler for route \(request.route.pathComponents) was found, but only sync routes " +
"should be registerable on this OS version. Handler: \(handler)")
"should be registrable on this OS version. Handler: \(handler)")
}
}

Expand Down
2 changes: 0 additions & 2 deletions Sources/SecureXPC/XPCError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ public enum XPCError: Error, Codable {
///
/// The associated value describes this decoding error.
case decodingError(String)
/// The route can't be registered because a route with this path already exists.
case routeAlreadyRegistered([String])
/// The route associated with the incoming XPC request is not registered with the ``XPCServer``.
case routeNotRegistered([String])
/// While the route associated with the incoming XPC request is registered with the ``XPCServer``, the message and/or reply does not match the handler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class RoundTripIntegrationTest: XCTestCase {
let replyBlockWasCalled = self.expectation(description: "The echo reply was received")

let echoRoute = XPCRoute.named("echo").withMessageType(String.self).withReplyType(String.self)
try anonymousServer.registerRoute(echoRoute) { msg in
anonymousServer.registerRoute(echoRoute) { msg in
remoteHandlerWasCalled.fulfill()
return "echo: \(msg)"
}
Expand All @@ -44,14 +44,14 @@ class RoundTripIntegrationTest: XCTestCase {

func testSendWithMessageWithReply_AsyncClient_SyncServer() async throws {
let echoRoute = XPCRoute.named("echo").withMessageType(String.self).withReplyType(String.self)
try anonymousServer.registerRoute(echoRoute) { msg in "echo: \(msg)" }
anonymousServer.registerRoute(echoRoute) { msg in "echo: \(msg)" }
let result = try await xpcClient.sendMessage("Hello, world!", route: echoRoute)
XCTAssertEqual(result, "echo: Hello, world!")
}

func testSendWithMessageWithReply_AsyncClient_AsyncServer() async throws {
let echoRoute = XPCRoute.named("echo").withMessageType(String.self).withReplyType(String.self)
try anonymousServer.registerRoute(echoRoute) { (msg: String) async -> String in
anonymousServer.registerRoute(echoRoute) { (msg: String) async -> String in
"echo: \(msg)"
}
let result = try await xpcClient.sendMessage("Hello, world!", route: echoRoute)
Expand All @@ -63,7 +63,7 @@ class RoundTripIntegrationTest: XCTestCase {
let replyBlockWasCalled = self.expectation(description: "The pong reply was received")

let pingRoute = XPCRoute.named("ping").withReplyType(String.self)
try anonymousServer.registerRoute(pingRoute) {
anonymousServer.registerRoute(pingRoute) {
remoteHandlerWasCalled.fulfill()
return "pong"
}
Expand All @@ -82,14 +82,14 @@ class RoundTripIntegrationTest: XCTestCase {

func testSendWithoutMessageWithReply_AsyncClient_SyncServer() async throws {
let pingRoute = XPCRoute.named("ping").withReplyType(String.self)
try anonymousServer.registerRoute(pingRoute) { "pong" }
anonymousServer.registerRoute(pingRoute) { "pong" }
let result = try await xpcClient.send(route: pingRoute)
XCTAssertEqual(result, "pong")
}

func testSendWithoutMessageWithReply_AsyncClient_AsyncServer() async throws {
let pingRoute = XPCRoute.named("ping").withReplyType(String.self)
try anonymousServer.registerRoute(pingRoute) { () async -> String in
anonymousServer.registerRoute(pingRoute) { () async -> String in
"pong"
}
let result = try await xpcClient.send(route: pingRoute)
Expand All @@ -100,7 +100,7 @@ class RoundTripIntegrationTest: XCTestCase {
let remoteHandlerWasCalled = self.expectation(description: "The remote handler was called")

let msgNoReplyRoute = XPCRoute.named("msgNoReplyRoute").withMessageType(String.self)
try anonymousServer.registerRoute(msgNoReplyRoute) { msg in
anonymousServer.registerRoute(msgNoReplyRoute) { msg in
XCTAssertEqual(msg, "Hello, world!")
remoteHandlerWasCalled.fulfill()
}
Expand All @@ -115,7 +115,7 @@ class RoundTripIntegrationTest: XCTestCase {
let responseBlockWasCalled = self.expectation(description: "The response was received")

let msgNoReplyRoute = XPCRoute.named("msgNoReplyRoute").withMessageType(String.self)
try anonymousServer.registerRoute(msgNoReplyRoute) { msg in
anonymousServer.registerRoute(msgNoReplyRoute) { msg in
XCTAssertEqual(msg, "Hello, world!")
remoteHandlerWasCalled.fulfill()
}
Expand All @@ -133,7 +133,7 @@ class RoundTripIntegrationTest: XCTestCase {
func testSendWithMessageWithoutReply_AsyncClient_SyncServer() async throws {
let remoteHandlerWasCalled = self.expectation(description: "The remote handler was called")
let msgNoReplyRoute = XPCRoute.named("msgNoReplyRoute").withMessageType(String.self)
try anonymousServer.registerRoute(msgNoReplyRoute) { msg in
anonymousServer.registerRoute(msgNoReplyRoute) { msg in
XCTAssertEqual(msg, "Hello, world!")
remoteHandlerWasCalled.fulfill()
}
Expand All @@ -145,7 +145,7 @@ class RoundTripIntegrationTest: XCTestCase {
func testSendWithMessageWithoutReply_AsyncClient_AsyncServer() async throws {
let remoteHandlerWasCalled = self.expectation(description: "The remote handler was called")
let msgNoReplyRoute = XPCRoute.named("msgNoReplyRoute").withMessageType(String.self)
try anonymousServer.registerRoute(msgNoReplyRoute) { (msg: String) async -> Void in
anonymousServer.registerRoute(msgNoReplyRoute) { (msg: String) async -> Void in
XCTAssertEqual(msg, "Hello, world!")
remoteHandlerWasCalled.fulfill()
}
Expand All @@ -158,7 +158,7 @@ class RoundTripIntegrationTest: XCTestCase {
let remoteHandlerWasCalled = self.expectation(description: "The remote handler was called")

let noMsgNoReplyRoute = XPCRoute.named("noMsgNoReplyRoute")
try anonymousServer.registerRoute(noMsgNoReplyRoute) {
anonymousServer.registerRoute(noMsgNoReplyRoute) {
remoteHandlerWasCalled.fulfill()
}

Expand All @@ -172,7 +172,7 @@ class RoundTripIntegrationTest: XCTestCase {
let responseBlockWasCalled = self.expectation(description: "The response was received")

let noMsgNoReplyRoute = XPCRoute.named("noMsgNoReplyRoute")
try anonymousServer.registerRoute(noMsgNoReplyRoute) {
anonymousServer.registerRoute(noMsgNoReplyRoute) {
remoteHandlerWasCalled.fulfill()
}

Expand All @@ -189,7 +189,7 @@ class RoundTripIntegrationTest: XCTestCase {
func testSendWithoutMessageWithoutReply_AsyncClient_SyncServer() async throws {
let remoteHandlerWasCalled = self.expectation(description: "The remote handler was called")
let noMsgNoReplyRoute = XPCRoute.named("noMsgNoReplyRoute")
try anonymousServer.registerRoute(noMsgNoReplyRoute) {
anonymousServer.registerRoute(noMsgNoReplyRoute) {
remoteHandlerWasCalled.fulfill()
}
try await xpcClient.send(route: noMsgNoReplyRoute)
Expand All @@ -200,7 +200,7 @@ class RoundTripIntegrationTest: XCTestCase {
func testSendWithoutMessageWithoutReply_AsyncClient_AsyncServer() async throws {
let remoteHandlerWasCalled = self.expectation(description: "The remote handler was called")
let noMsgNoReplyRoute = XPCRoute.named("noMsgNoReplyRoute")
try anonymousServer.registerRoute(noMsgNoReplyRoute, handler: { () async -> Void in
anonymousServer.registerRoute(noMsgNoReplyRoute, handler: { () async -> Void in
remoteHandlerWasCalled.fulfill()
})
try await xpcClient.send(route: noMsgNoReplyRoute)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class ServerTerminationIntegrationTest: XCTestCase {
// Server & client setup
let route = XPCRoute.named("doNothing")
let server = XPCServer.makeAnonymous(clientRequirements: dummyRequirements)
try server.registerRoute(route) { }
server.registerRoute(route) { }
server.start()
let client = XPCClient.forEndpoint(server.endpoint)

Expand Down

0 comments on commit 888c5a3

Please sign in to comment.