From 0b118976171d3c50787e0e28a96b845d687bec65 Mon Sep 17 00:00:00 2001 From: Luke Howard Date: Fri, 12 Jan 2024 06:56:44 +1100 Subject: [PATCH] device spy test --- .../IORingDeviceSpy/IORingDeviceSpy.swift | 69 +++++++++++ Package.swift | 6 + Sources/IORingUtils/Tty.swift | 107 ++++++++++++++++++ 3 files changed, 182 insertions(+) create mode 100644 Examples/IORingDeviceSpy/IORingDeviceSpy.swift create mode 100644 Sources/IORingUtils/Tty.swift diff --git a/Examples/IORingDeviceSpy/IORingDeviceSpy.swift b/Examples/IORingDeviceSpy/IORingDeviceSpy.swift new file mode 100644 index 0000000..141af5e --- /dev/null +++ b/Examples/IORingDeviceSpy/IORingDeviceSpy.swift @@ -0,0 +1,69 @@ +// +// Copyright (c) 2024 PADL Software Pty Ltd +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Glibc +import IORing +import IORingUtils + +@main +public struct IORingDeviceSpy { + private let blockSize: Int + private let ring: IORing + + public static func main() async throws { + if CommandLine.arguments.count < 2 { + print("Usage: \(CommandLine.arguments[0]) [device] <[block_size]>") + exit(1) + } + + let blockSize: Int + + if CommandLine.arguments.count > 2 { + blockSize = Int(CommandLine.arguments[2])! + } else { + blockSize = 1 + } + + let spy = try await IORingDeviceSpy(blockSize: blockSize) + let device = CommandLine.arguments[1] + + try await spy.spy(device: device) + } + + init(blockSize: Int = 1) async throws { + ring = IORing.shared + self.blockSize = blockSize + + try await ring.registerFixedBuffers(count: 1, size: blockSize) + } + + func spy(device: String) async throws { + let fd = try FileHandle(fileDescriptor: open(device, O_RDONLY), closeOnDealloc: true) + + try fd.setBlocking(false) + if fd.isATty { + var tty = try fd.getTty() + try tty.setN81(speed: 115_200) + try fd.set(tty: tty) + } + + repeat { + try await ring.readFixed(count: blockSize, bufferIndex: 0, from: fd) { + print($0.map { String(format: "%02x", $0) }.joined()) + } + } while !Task.isCancelled + } +} diff --git a/Package.swift b/Package.swift index aa18663..ae0bcc0 100644 --- a/Package.swift +++ b/Package.swift @@ -147,6 +147,12 @@ let package = Package( path: "Examples/IORingUDPServer", linkerSettings: [] + ASANLinkerSettings ), + .executableTarget( + name: "IORingDeviceSpy", + dependencies: ["IORing", "IORingUtils"], + path: "Examples/IORingDeviceSpy", + linkerSettings: [] + ASANLinkerSettings + ), ], cLanguageStandard: .c18, cxxLanguageStandard: .cxx20 diff --git a/Sources/IORingUtils/Tty.swift b/Sources/IORingUtils/Tty.swift new file mode 100644 index 0000000..26bef9d --- /dev/null +++ b/Sources/IORingUtils/Tty.swift @@ -0,0 +1,107 @@ +// +// Copyright (c) 2024 PADL Software Pty Ltd +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Glibc +import IORing + +public enum Parity { + case none + case odd + case even +} + +public enum StopBits { + case one + case two +} + +public enum DataBits { + case seven + case eight +} + +public extension termios { + mutating func setN81(speed: UInt32) throws { + try makeRaw() + try set(speed: speed) + set(parity: .none) + set(stopBits: .one) + set(dataBits: .eight) + } + + mutating func makeRaw() throws { + cfmakeraw(&self) + } + + mutating func set(speed: UInt32) throws { + var tty = self + try Errno.throwingErrno { + cfsetspeed(&tty, speed) + } + self = tty + } + + mutating func set(parity: Parity) { + switch parity { + case .none: + c_cflag &= ~tcflag_t(PARENB | PARODD) + case .odd: + c_cflag |= tcflag_t(PARENB | PARODD) + case .even: + c_cflag |= tcflag_t(PARENB) + c_cflag &= ~tcflag_t(PARODD) + } + } + + mutating func set(stopBits: StopBits) { + switch stopBits { + case .two: + c_cflag |= tcflag_t(CSTOPB) + case .one: + c_cflag &= ~tcflag_t(CSTOPB) + } + } + + mutating func set(dataBits: DataBits) { + switch dataBits { + case .eight: + c_cflag |= tcflag_t(CS8) + case .seven: + c_cflag |= tcflag_t(CS7) + } + } +} + +public extension FileDescriptorRepresentable { + func set(tty: termios) throws { + var tty = tty + try Errno.throwingErrno { + tcsetattr(self.fileDescriptor, TCSANOW, &tty) + } + } + + func getTty() throws -> termios { + var tty = termios() + try Errno.throwingErrno { + tcgetattr(self.fileDescriptor, &tty) + } + return tty + } + + var isATty: Bool { + isatty(fileDescriptor) != 0 + } +}