Opinionated pure Swift controller for Tor, including full support for Swift 5.5 and Swift Concurrency
BCSwiftTor
Opinionated pure Swift controller for Tor, including full support for Swift 5.5 and Swift Concurrency.
Inspired by Tor.framework but designed to be used seamlessly with Blockchain Commons projects and technologies.
Dependencies
Depends on BCSwiftTorBase, which is a thin wrapper around Tor that has a new build system for building a universal XCFramework for use with MacOSX, Mac Catalyst, iOS devices, and the iOS simulator across Intel and Apple Silicon (ARM).
Building
Add to your project like any other Swift Package.
Usage
import XCTest
import Tor
class ReadMeTests: XCTestCase {
var controller: TorController!
// Configure the controller. Socket files have length limitations, so find a file system path
// that's short enough to accomodate the socket file we use to communicate with the Tor thread.
static let configuration: TorConfiguration = {
var homeDirectory: URL!
#if targetEnvironment(simulator)
for variable in ["IPHONE_SIMULATOR_HOST_HOME", "SIMULATOR_HOST_HOME"] {
if let p = getenv(variable) {
homeDirectory = URL(fileURLWithPath: String(cString: p))
break
}
}
#else
homeDirectory = URL(fileURLWithPath: NSHomeDirectory())
#endif
precondition(homeDirectory != nil)
let fileManager = FileManager.default
let dataDirectory = fileManager.temporaryDirectory
let socketDirectory = homeDirectory.appendingPathComponent(".tor")
try! fileManager.createDirectory(at: socketDirectory, withIntermediateDirectories: true)
let socketFile = socketDirectory.appendingPathComponent("control_port")
return TorConfiguration(
dataDirectory: dataDirectory,
controlSocket: socketFile,
options: [.ignoreMissingTorrc, .cookieAuthentication]
)
}()
// Run once for every all tests in this suite. Start the Tor thread with logging back to the app.
static override func setUp() {
super.setUp()
let runner = TorRunner(configuration: Self.configuration)
runner.setLogging(minLogSeverity: .notice) { severity, domain, msg in
print("? [\(severity)]: \(domain): \(msg)")
}
runner.run()
}
// Run once for each test this suite. Create a new Tor controller object.
override func setUp() async throws {
try await super.setUp()
controller = try await TorController(socket: Self.configuration.controlSocket)
}
// Authenticate to the Tor process using the cookie that it writes to the file system.
func authenticate() async throws {
guard let cookie = Self.configuration.cookie else {
XCTFail("No cookie file found.")
return
}
try await controller.authenticate(with: cookie)
}
func testRetrieveURL() async throws {
// Authenticate and then wait until a circuit is established
try await authenticate()
try await controller.untilCircuitEstablished()
// Create a URL session that communicates using the socket that's been set up.
let sessionConfiguration = try await controller.getSessionConfiguration()
let session = URLSession(configuration: sessionConfiguration)
// Use the URLSession to retrieve data from an Onion address URL via Tor.
// Blockchain Commons SpotBit instance.
// https://github.com/blockchaincommons/spotbit#test-server
let url = URL(string: "http://h6zwwkcivy2hjys6xpinlnz2f74dsmvltzsd4xb42vinhlcaoe7fdeqd.onion/status")!
let (data, resp) = try await session.data(from: url)
let response = resp as! HTTPURLResponse
XCTAssertEqual(response.statusCode, 200)
XCTAssertEqual(data.utf8, "server is running")
}
}