From 26fe7aa4d8a179a9e32c64d3be4fb670308a922a Mon Sep 17 00:00:00 2001 From: raghavroy145 <raghavroy145@gmail.com> Date: Thu, 20 Mar 2025 10:56:07 +0000 Subject: [PATCH 1/4] Add AsynchronizedFileSink for non-blocking PCAP recording Motivation: The current PCAP recording functionality uses SynchronizedFileSink, which blocks the event loop, this patch attempts an introduction of AsynchronizedFileSink,which uses Swift's concurrency model to perform non-blocking file writes. This patch attempts to address https://github.com/swift-server/async-http-client/issues/239. This change also ensures that debugging via PCAP recording remains accessible while preventing the event loop from stalling. Modifications: - Implemented AsynchronizedFileSink thread-safe, non-blocking writes. - Provided unit tests to verify correct behavior. Result: - Users can enable non-blocking PCAP recording using AsynchronizedFileSink, improving debugging without risking connection pool performance. - Test coverage ensures the new implementation works as expected. --- Sources/NIOExtras/WritePCAPHandler.swift | 125 ++++++++++++++++++ .../NIOExtrasTests/WritePCAPHandlerTest.swift | 61 +++++++++ 2 files changed, 186 insertions(+) diff --git a/Sources/NIOExtras/WritePCAPHandler.swift b/Sources/NIOExtras/WritePCAPHandler.swift index 914309b..4b28152 100644 --- a/Sources/NIOExtras/WritePCAPHandler.swift +++ b/Sources/NIOExtras/WritePCAPHandler.swift @@ -808,6 +808,131 @@ extension NIOWritePCAPHandler { } } } + + public final class AsynchronizedFileSink { + private let fileHandle: NIOFileHandle + private let eventLoop: EventLoop + private let errorHandler: (Swift.Error) -> Void + private var state: State = .running + + public enum FileWritingMode { + case appendToExistingPCAPFile + case createNewPCAPFile + } + + public struct Error: Swift.Error { + public var errorCode: Int + + internal enum ErrorCode: Int { + case cannotOpenFileError = 1 + case cannotWriteToFileError + } + } + + private enum State { + case running + case error(Swift.Error) + } + + /// Creates an AsynchronizedFileSink for writing to a .pcap file at the given path. + /// If fileWritingMode is `.createNewPCAPFile`, a file header is written. + public static func fileSinkWritingToFile( + path: String, + fileWritingMode: FileWritingMode = .createNewPCAPFile, + errorHandler: @escaping (Swift.Error) -> Void, + on eventLoop: EventLoop + ) async throws -> AsynchronizedFileSink { + let oflag: CInt = fileWritingMode == .createNewPCAPFile ? (O_TRUNC | O_CREAT) : O_APPEND + let fd: CInt = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<CInt, Swift.Error>) in + path.withCString { pathPtr in + let fd: Int32 = open(pathPtr, O_WRONLY | oflag, 0o600) + if fd < 0 { + continuation.resume(throwing: Error(errorCode: Error.ErrorCode.cannotOpenFileError.rawValue)) + } else { + continuation.resume(returning: fd) + } + } + } + + if fileWritingMode == .createNewPCAPFile { + _ = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<CInt, Swift.Error>) in + NIOWritePCAPHandler.pcapFileHeader.withUnsafeReadableBytes { ptr in + let writeOk: Bool = sysWrite(fd, ptr.baseAddress, ptr.count) == ptr.count + if writeOk { + continuation.resume(returning: 0) // Dummy value + } else { + continuation.resume(throwing: Error(errorCode: Error.ErrorCode.cannotWriteToFileError.rawValue)) + } + } + } + } + let fileHandle: NIOFileHandle = NIOFileHandle(_deprecatedTakingOwnershipOfDescriptor: fd) + return AsynchronizedFileSink(fileHandle: fileHandle, eventLoop: eventLoop, errorHandler: errorHandler) + } + + private init( + fileHandle: NIOFileHandle, + eventLoop: EventLoop, + errorHandler: @escaping (Swift.Error) -> Void + ) { + self.fileHandle = fileHandle + self.eventLoop = eventLoop + self.errorHandler = errorHandler + } + + public func write(buffer: ByteBuffer) async throws { + try await self.eventLoop.submit { + try self.fileHandle.withUnsafeFileDescriptor { fd in + var buffer = buffer + while buffer.readableBytes > 0 { + try buffer.readWithUnsafeReadableBytes { dataPtr in + let written = sysWrite(fd, dataPtr.baseAddress, dataPtr.count) + guard written > 0 else { + throw Error(errorCode: Error.ErrorCode.cannotWriteToFileError.rawValue) + } + return written + } + } + } + }.get() + } + + /// Asynchronously syncs the file to disk using fsync. + public func asyncSync() async throws { + try await withCheckedThrowingContinuation { continuation in + _ = self.eventLoop.submit { + do { + try self.fileHandle.withUnsafeFileDescriptor { fd in + let result: CInt = fsync(fd) + if result != 0 { + throw Error(errorCode: Error.ErrorCode.cannotWriteToFileError.rawValue) + } + } + continuation.resume(returning: ()) + } catch { + continuation.resume(throwing: error) + } + } + } + } + + /// Asynchronously closes the file sink. + public func close() async throws { + try await withCheckedThrowingContinuation { continuation in + _ = self.eventLoop.submit { + do { + try self.fileHandle.close() + continuation.resume(returning: ()) + print("File successfully closed.") + } catch { + continuation.resume(throwing: error) + print("Error closing file: \(error)") + } + } + } + } + } } extension NIOWritePCAPHandler.SynchronizedFileSink: @unchecked Sendable {} +extension NIOWritePCAPHandler.AsynchronizedFileSink: @unchecked Sendable {} \ No newline at end of file diff --git a/Tests/NIOExtrasTests/WritePCAPHandlerTest.swift b/Tests/NIOExtrasTests/WritePCAPHandlerTest.swift index 72f11a3..46844f9 100644 --- a/Tests/NIOExtrasTests/WritePCAPHandlerTest.swift +++ b/Tests/NIOExtrasTests/WritePCAPHandlerTest.swift @@ -17,6 +17,7 @@ import Foundation import NIOCore import NIOEmbedded import XCTest +import NIOPosix @testable import NIOExtras @@ -816,6 +817,66 @@ class WritePCAPHandlerTest: XCTestCase { XCTAssertNoThrow(XCTAssertTrue(try channel.finish().isClean)) } + func testAsynchronizedFileSinkWritesDataToFile() async throws { + // Create a unique temporary file path. + let testHostname: String = "testhost" + let filePath: String = "/tmp/packets-\(testHostname)-\(UUID())-\(getpid())-\(Int(Date().timeIntervalSince1970)).pcap" + + let eventLoop: EmbeddedEventLoop = EmbeddedEventLoop() + + // Create the asynchronous file sink using our new API. + let fileSink: NIOWritePCAPHandler.AsynchronizedFileSink = try await NIOWritePCAPHandler.AsynchronizedFileSink.fileSinkWritingToFile( + path: filePath, + fileWritingMode: .createNewPCAPFile, + errorHandler: { error in XCTFail("PCAP logging error: \(error)") }, + on: eventLoop + ) + + // Create an EmbeddedChannel that uses a NIOWritePCAPHandler with our async file sink. + let channel: EmbeddedChannel = EmbeddedChannel(handler: NIOWritePCAPHandler( + mode: .client, + fakeLocalAddress: nil, + fakeRemoteAddress: nil, + fileSink: { buffer in + Task.detached { + do { + try await fileSink.write(buffer: buffer) + } catch { + XCTFail("Failed to write to file sink: \(error)") + } + } + } + )) + + var buffer: ByteBuffer = channel.allocator.buffer(capacity: 64) + buffer.writeString("Test PCAP data") + try await fileSink.write(buffer: buffer) + + // Close the channel + try await channel.closeFuture.get() + + // Flush any buffered data to disk and close the file. + try await fileSink.asyncSync() + // try await fileSink.close() + let expectation: XCTestExpectation = XCTestExpectation(description: "File sink should close") + Task { + do { + try await fileSink.close() + expectation.fulfill() + } catch { + XCTFail("Failed to close file sink: \(error)") + } + } + await fulfillment(of: [expectation], timeout: 5) + + // Verify that the file exists and contains data. + let fileManager: FileManager = FileManager.default + let fileData: Data = try Data(contentsOf: URL(fileURLWithPath: filePath)) + XCTAssertGreaterThan(fileData.count, 0, "PCAP file should contain data") + + // Clean up the temporary file. + try fileManager.removeItem(atPath: filePath) + } } struct PCAPRecord { From 6a3857974d3b98a30f1dbaeebe5031939267ebd4 Mon Sep 17 00:00:00 2001 From: raghavroy145 <raghavroy145@gmail.com> Date: Thu, 20 Mar 2025 22:33:09 +0000 Subject: [PATCH 2/4] Resolved correct handling of synchronous blocks as per reviews --- Sources/NIOExtras/WritePCAPHandler.swift | 97 +++++++------------ .../NIOExtrasTests/WritePCAPHandlerTest.swift | 5 - 2 files changed, 35 insertions(+), 67 deletions(-) diff --git a/Sources/NIOExtras/WritePCAPHandler.swift b/Sources/NIOExtras/WritePCAPHandler.swift index 4b28152..153c093 100644 --- a/Sources/NIOExtras/WritePCAPHandler.swift +++ b/Sources/NIOExtras/WritePCAPHandler.swift @@ -15,6 +15,7 @@ import CNIOLinux import Dispatch import NIOCore +import NIOConcurrencyHelpers #if canImport(Darwin) import Darwin @@ -808,12 +809,11 @@ extension NIOWritePCAPHandler { } } } - public final class AsynchronizedFileSink { private let fileHandle: NIOFileHandle private let eventLoop: EventLoop - private let errorHandler: (Swift.Error) -> Void - private var state: State = .running + private let errorHandler: @Sendable (Swift.Error) -> Void + private let state: NIOLockedValueBox<State> = NIOLockedValueBox(.running) public enum FileWritingMode { case appendToExistingPCAPFile @@ -839,33 +839,27 @@ extension NIOWritePCAPHandler { public static func fileSinkWritingToFile( path: String, fileWritingMode: FileWritingMode = .createNewPCAPFile, - errorHandler: @escaping (Swift.Error) -> Void, + errorHandler: @escaping @Sendable (Swift.Error) -> Void, on eventLoop: EventLoop ) async throws -> AsynchronizedFileSink { let oflag: CInt = fileWritingMode == .createNewPCAPFile ? (O_TRUNC | O_CREAT) : O_APPEND - let fd: CInt = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<CInt, Swift.Error>) in - path.withCString { pathPtr in - let fd: Int32 = open(pathPtr, O_WRONLY | oflag, 0o600) - if fd < 0 { - continuation.resume(throwing: Error(errorCode: Error.ErrorCode.cannotOpenFileError.rawValue)) - } else { - continuation.resume(returning: fd) - } - } + let fd: CInt = path.withCString { pathPtr in + open(pathPtr, O_WRONLY | oflag, 0o600) + } + if fd < 0 { + throw Error(errorCode: Error.ErrorCode.cannotOpenFileError.rawValue) } + /// Write PCAP file header if fileWritingMode == .createNewPCAPFile { - _ = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<CInt, Swift.Error>) in - NIOWritePCAPHandler.pcapFileHeader.withUnsafeReadableBytes { ptr in - let writeOk: Bool = sysWrite(fd, ptr.baseAddress, ptr.count) == ptr.count - if writeOk { - continuation.resume(returning: 0) // Dummy value - } else { - continuation.resume(throwing: Error(errorCode: Error.ErrorCode.cannotWriteToFileError.rawValue)) - } - } + let writeOk: Bool = NIOWritePCAPHandler.pcapFileHeader.withUnsafeReadableBytes { ptr in + sysWrite(fd, ptr.baseAddress, ptr.count) == ptr.count + } + if !writeOk { + throw Error(errorCode: Error.ErrorCode.cannotWriteToFileError.rawValue) } } + let fileHandle: NIOFileHandle = NIOFileHandle(_deprecatedTakingOwnershipOfDescriptor: fd) return AsynchronizedFileSink(fileHandle: fileHandle, eventLoop: eventLoop, errorHandler: errorHandler) } @@ -873,7 +867,7 @@ extension NIOWritePCAPHandler { private init( fileHandle: NIOFileHandle, eventLoop: EventLoop, - errorHandler: @escaping (Swift.Error) -> Void + errorHandler: @escaping @Sendable (Swift.Error) -> Void ) { self.fileHandle = fileHandle self.eventLoop = eventLoop @@ -881,58 +875,37 @@ extension NIOWritePCAPHandler { } public func write(buffer: ByteBuffer) async throws { - try await self.eventLoop.submit { - try self.fileHandle.withUnsafeFileDescriptor { fd in - var buffer = buffer - while buffer.readableBytes > 0 { - try buffer.readWithUnsafeReadableBytes { dataPtr in - let written = sysWrite(fd, dataPtr.baseAddress, dataPtr.count) - guard written > 0 else { - throw Error(errorCode: Error.ErrorCode.cannotWriteToFileError.rawValue) - } - return written + try self.fileHandle.withUnsafeFileDescriptor { fd in + var buffer = buffer + while buffer.readableBytes > 0 { + try buffer.readWithUnsafeReadableBytes { dataPtr in + let written = sysWrite(fd, dataPtr.baseAddress, dataPtr.count) + guard written > 0 else { + throw Error(errorCode: Error.ErrorCode.cannotWriteToFileError.rawValue) } + return written } } - }.get() + } } - /// Asynchronously syncs the file to disk using fsync. + /// Syncs the file to disk using fsync. public func asyncSync() async throws { - try await withCheckedThrowingContinuation { continuation in - _ = self.eventLoop.submit { - do { - try self.fileHandle.withUnsafeFileDescriptor { fd in - let result: CInt = fsync(fd) - if result != 0 { - throw Error(errorCode: Error.ErrorCode.cannotWriteToFileError.rawValue) - } - } - continuation.resume(returning: ()) - } catch { - continuation.resume(throwing: error) - } + try self.fileHandle.withUnsafeFileDescriptor { fd in + let result: CInt = fsync(fd) + if result != 0 { + throw Error(errorCode: Error.ErrorCode.cannotWriteToFileError.rawValue) } } } - /// Asynchronously closes the file sink. + /// Closes the file sink. public func close() async throws { - try await withCheckedThrowingContinuation { continuation in - _ = self.eventLoop.submit { - do { - try self.fileHandle.close() - continuation.resume(returning: ()) - print("File successfully closed.") - } catch { - continuation.resume(throwing: error) - print("Error closing file: \(error)") - } - } - } + try self.fileHandle.close() + print("File successfully closed.") } } } extension NIOWritePCAPHandler.SynchronizedFileSink: @unchecked Sendable {} -extension NIOWritePCAPHandler.AsynchronizedFileSink: @unchecked Sendable {} \ No newline at end of file +extension NIOWritePCAPHandler.AsynchronizedFileSink: Sendable {} \ No newline at end of file diff --git a/Tests/NIOExtrasTests/WritePCAPHandlerTest.swift b/Tests/NIOExtrasTests/WritePCAPHandlerTest.swift index 46844f9..086e065 100644 --- a/Tests/NIOExtrasTests/WritePCAPHandlerTest.swift +++ b/Tests/NIOExtrasTests/WritePCAPHandlerTest.swift @@ -818,13 +818,11 @@ class WritePCAPHandlerTest: XCTestCase { } func testAsynchronizedFileSinkWritesDataToFile() async throws { - // Create a unique temporary file path. let testHostname: String = "testhost" let filePath: String = "/tmp/packets-\(testHostname)-\(UUID())-\(getpid())-\(Int(Date().timeIntervalSince1970)).pcap" let eventLoop: EmbeddedEventLoop = EmbeddedEventLoop() - // Create the asynchronous file sink using our new API. let fileSink: NIOWritePCAPHandler.AsynchronizedFileSink = try await NIOWritePCAPHandler.AsynchronizedFileSink.fileSinkWritingToFile( path: filePath, fileWritingMode: .createNewPCAPFile, @@ -832,7 +830,6 @@ class WritePCAPHandlerTest: XCTestCase { on: eventLoop ) - // Create an EmbeddedChannel that uses a NIOWritePCAPHandler with our async file sink. let channel: EmbeddedChannel = EmbeddedChannel(handler: NIOWritePCAPHandler( mode: .client, fakeLocalAddress: nil, @@ -851,8 +848,6 @@ class WritePCAPHandlerTest: XCTestCase { var buffer: ByteBuffer = channel.allocator.buffer(capacity: 64) buffer.writeString("Test PCAP data") try await fileSink.write(buffer: buffer) - - // Close the channel try await channel.closeFuture.get() // Flush any buffered data to disk and close the file. From 35ff8e85944fd4899065e77008e2b4cb8f4e8df2 Mon Sep 17 00:00:00 2001 From: raghavroy145 <raghavroy145@gmail.com> Date: Sat, 22 Mar 2025 17:42:27 +0000 Subject: [PATCH 3/4] Test passes: Simplified test to not use an EmbeddedChannel --- .../NIOExtrasTests/WritePCAPHandlerTest.swift | 38 +++---------------- 1 file changed, 6 insertions(+), 32 deletions(-) diff --git a/Tests/NIOExtrasTests/WritePCAPHandlerTest.swift b/Tests/NIOExtrasTests/WritePCAPHandlerTest.swift index 086e065..73c8ff2 100644 --- a/Tests/NIOExtrasTests/WritePCAPHandlerTest.swift +++ b/Tests/NIOExtrasTests/WritePCAPHandlerTest.swift @@ -830,47 +830,21 @@ class WritePCAPHandlerTest: XCTestCase { on: eventLoop ) - let channel: EmbeddedChannel = EmbeddedChannel(handler: NIOWritePCAPHandler( - mode: .client, - fakeLocalAddress: nil, - fakeRemoteAddress: nil, - fileSink: { buffer in - Task.detached { - do { - try await fileSink.write(buffer: buffer) - } catch { - XCTFail("Failed to write to file sink: \(error)") - } - } - } - )) - - var buffer: ByteBuffer = channel.allocator.buffer(capacity: 64) + // Write test data directly using the file sink. + var buffer = ByteBufferAllocator().buffer(capacity: 64) buffer.writeString("Test PCAP data") try await fileSink.write(buffer: buffer) - try await channel.closeFuture.get() - // Flush any buffered data to disk and close the file. + // Sync and then close the file sink. try await fileSink.asyncSync() - // try await fileSink.close() - let expectation: XCTestExpectation = XCTestExpectation(description: "File sink should close") - Task { - do { - try await fileSink.close() - expectation.fulfill() - } catch { - XCTFail("Failed to close file sink: \(error)") - } - } - await fulfillment(of: [expectation], timeout: 5) + try await fileSink.close() // Verify that the file exists and contains data. - let fileManager: FileManager = FileManager.default - let fileData: Data = try Data(contentsOf: URL(fileURLWithPath: filePath)) + let fileData = try Data(contentsOf: URL(fileURLWithPath: filePath)) XCTAssertGreaterThan(fileData.count, 0, "PCAP file should contain data") // Clean up the temporary file. - try fileManager.removeItem(atPath: filePath) + try FileManager.default.removeItem(atPath: filePath) } } From f6c2bc355f073d437fe833a54b5874d7f04fd775 Mon Sep 17 00:00:00 2001 From: raghavroy145 <raghavroy145@gmail.com> Date: Thu, 27 Mar 2025 09:53:52 +0000 Subject: [PATCH 4/4] Ran local formatting --- Sources/NIOExtras/WritePCAPHandler.swift | 20 ++++++------- .../NIOExtrasTests/WritePCAPHandlerTest.swift | 28 ++++++++++--------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/Sources/NIOExtras/WritePCAPHandler.swift b/Sources/NIOExtras/WritePCAPHandler.swift index 153c093..47e6417 100644 --- a/Sources/NIOExtras/WritePCAPHandler.swift +++ b/Sources/NIOExtras/WritePCAPHandler.swift @@ -14,8 +14,8 @@ import CNIOLinux import Dispatch -import NIOCore import NIOConcurrencyHelpers +import NIOCore #if canImport(Darwin) import Darwin @@ -814,12 +814,12 @@ extension NIOWritePCAPHandler { private let eventLoop: EventLoop private let errorHandler: @Sendable (Swift.Error) -> Void private let state: NIOLockedValueBox<State> = NIOLockedValueBox(.running) - + public enum FileWritingMode { case appendToExistingPCAPFile case createNewPCAPFile } - + public struct Error: Swift.Error { public var errorCode: Int @@ -828,12 +828,12 @@ extension NIOWritePCAPHandler { case cannotWriteToFileError } } - + private enum State { case running case error(Swift.Error) } - + /// Creates an AsynchronizedFileSink for writing to a .pcap file at the given path. /// If fileWritingMode is `.createNewPCAPFile`, a file header is written. public static func fileSinkWritingToFile( @@ -849,7 +849,7 @@ extension NIOWritePCAPHandler { if fd < 0 { throw Error(errorCode: Error.ErrorCode.cannotOpenFileError.rawValue) } - + /// Write PCAP file header if fileWritingMode == .createNewPCAPFile { let writeOk: Bool = NIOWritePCAPHandler.pcapFileHeader.withUnsafeReadableBytes { ptr in @@ -863,7 +863,7 @@ extension NIOWritePCAPHandler { let fileHandle: NIOFileHandle = NIOFileHandle(_deprecatedTakingOwnershipOfDescriptor: fd) return AsynchronizedFileSink(fileHandle: fileHandle, eventLoop: eventLoop, errorHandler: errorHandler) } - + private init( fileHandle: NIOFileHandle, eventLoop: EventLoop, @@ -873,7 +873,7 @@ extension NIOWritePCAPHandler { self.eventLoop = eventLoop self.errorHandler = errorHandler } - + public func write(buffer: ByteBuffer) async throws { try self.fileHandle.withUnsafeFileDescriptor { fd in var buffer = buffer @@ -888,7 +888,7 @@ extension NIOWritePCAPHandler { } } } - + /// Syncs the file to disk using fsync. public func asyncSync() async throws { try self.fileHandle.withUnsafeFileDescriptor { fd in @@ -908,4 +908,4 @@ extension NIOWritePCAPHandler { } extension NIOWritePCAPHandler.SynchronizedFileSink: @unchecked Sendable {} -extension NIOWritePCAPHandler.AsynchronizedFileSink: Sendable {} \ No newline at end of file +extension NIOWritePCAPHandler.AsynchronizedFileSink: Sendable {} diff --git a/Tests/NIOExtrasTests/WritePCAPHandlerTest.swift b/Tests/NIOExtrasTests/WritePCAPHandlerTest.swift index 73c8ff2..e070765 100644 --- a/Tests/NIOExtrasTests/WritePCAPHandlerTest.swift +++ b/Tests/NIOExtrasTests/WritePCAPHandlerTest.swift @@ -16,8 +16,8 @@ import CNIOLinux import Foundation import NIOCore import NIOEmbedded -import XCTest import NIOPosix +import XCTest @testable import NIOExtras @@ -819,30 +819,32 @@ class WritePCAPHandlerTest: XCTestCase { func testAsynchronizedFileSinkWritesDataToFile() async throws { let testHostname: String = "testhost" - let filePath: String = "/tmp/packets-\(testHostname)-\(UUID())-\(getpid())-\(Int(Date().timeIntervalSince1970)).pcap" + let filePath: String = + "/tmp/packets-\(testHostname)-\(UUID())-\(getpid())-\(Int(Date().timeIntervalSince1970)).pcap" let eventLoop: EmbeddedEventLoop = EmbeddedEventLoop() - - let fileSink: NIOWritePCAPHandler.AsynchronizedFileSink = try await NIOWritePCAPHandler.AsynchronizedFileSink.fileSinkWritingToFile( - path: filePath, - fileWritingMode: .createNewPCAPFile, - errorHandler: { error in XCTFail("PCAP logging error: \(error)") }, - on: eventLoop - ) - + + let fileSink: NIOWritePCAPHandler.AsynchronizedFileSink = try await NIOWritePCAPHandler.AsynchronizedFileSink + .fileSinkWritingToFile( + path: filePath, + fileWritingMode: .createNewPCAPFile, + errorHandler: { error in XCTFail("PCAP logging error: \(error)") }, + on: eventLoop + ) + // Write test data directly using the file sink. var buffer = ByteBufferAllocator().buffer(capacity: 64) buffer.writeString("Test PCAP data") try await fileSink.write(buffer: buffer) - + // Sync and then close the file sink. try await fileSink.asyncSync() try await fileSink.close() - + // Verify that the file exists and contains data. let fileData = try Data(contentsOf: URL(fileURLWithPath: filePath)) XCTAssertGreaterThan(fileData.count, 0, "PCAP file should contain data") - + // Clean up the temporary file. try FileManager.default.removeItem(atPath: filePath) }