Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 81 additions & 3 deletions Sources/BluetoothLinux/BNEP/BNEP.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,86 @@
//
// File.swift
//
// BNEP.swift
// BluetoothLinux
//
// Created by Alsey Coleman Miller on 16/10/21.
// Bluetooth Network Encapsulation Protocol (PAN) control socket.
//

import Foundation
import Bluetooth
import SystemPackage
import Socket

/// BNEP control socket.
///
/// Manages kernel Bluetooth network encapsulation sessions, which bridge
/// connected L2CAP sockets (PSM 15) into virtual Ethernet interfaces
/// (e.g. `bnep0`) for Personal Area Networking.
public struct BNEPSocket: Sendable {

// MARK: - Properties

@usableFromInline
internal let fileDescriptor: SocketDescriptor

// MARK: - Initialization

/// Open a BNEP control socket.
public init() throws(Errno) {
self.fileDescriptor = try .bluetooth(.bnep, flags: [.closeOnExec])
}

// MARK: - Methods

/// Close the control socket.
///
/// Established sessions are not affected.
public func close() {
try? fileDescriptor.close()
}

/// Bridge a connected L2CAP socket into a kernel network interface.
///
/// The L2CAP socket must be connected to the remote device on the BNEP PSM (15)
/// and the setup connection request/response exchange must already have taken place
/// (or pass ``BNEPConnectionFlag/setupResponse`` to let the kernel reply to a
/// received setup request).
///
/// - Returns: The name of the created network interface (e.g. `bnep0`).
@discardableResult
public func addConnection(
_ socket: L2CAPSocket,
role: BNEPRole,
flags: BNEPConnectionFlag = [],
device: String = ""
) throws -> String {
try fileDescriptor.bnepAddConnection(
socket: socket.fileDescriptor,
flags: flags,
role: role,
device: device
)
}

/// Destroy the session (and network interface) for the specified remote device.
public func removeConnection(
destination: BluetoothAddress,
flags: BNEPConnectionFlag = []
) throws {
try fileDescriptor.bnepRemoveConnection(
destination: destination,
flags: flags
)
}

/// List the active sessions.
public func connections(
limit: Int = BNEPIO.GetConnectionList.maxLimit
) throws -> [BNEPConnection] {
try fileDescriptor.bnepConnectionList(limit: limit)
}

/// Read the feature bitmask supported by the kernel.
public func supportedFeatures() throws -> BNEPIO.GetSupportedFeatures {
try fileDescriptor.bnepSupportedFeatures()
}
}
57 changes: 57 additions & 0 deletions Sources/BluetoothLinux/BNEP/BNEPConnection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// BNEPConnection.swift
// BluetoothLinux
//

import Foundation
import Bluetooth
import SystemPackage
import Socket

/// BNEP connection information.
public struct BNEPConnection: Equatable, Hashable, Sendable {

/// Connection flags.
public let flags: BNEPConnectionFlag

/// Service role of the local device.
public let role: BNEPRole?

/// Connection state.
public let state: BNEPConnectionState

/// Address of the remote device.
public let destination: BluetoothAddress

/// Name of the network interface bridging the connection (e.g. `bnep0`).
public let device: String
}

internal extension BNEPConnection {

init(_ bytes: CInterop.BNEPConnectionInformation) {
self.flags = BNEPConnectionFlag(rawValue: bytes.flags)
self.role = BNEPRole(rawValue: bytes.role)
self.state = BNEPConnectionState(rawValue: bytes.state) ?? .unknown
self.destination = bytes.destination
self.device = withUnsafeBytes(of: bytes.device) { buffer in
String(decoding: buffer.prefix(while: { $0 != 0 }), as: UTF8.self)
}
}
}

internal extension String {

/// Encode as a fixed-size null-terminated C string tuple of 16 bytes.
@usableFromInline
var deviceNameBytes: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) {
var bytes: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
let utf8 = Array(self.utf8.prefix(15))
withUnsafeMutableBytes(of: &bytes) { buffer in
for (index, byte) in utf8.enumerated() {
buffer[index] = byte
}
}
return bytes
}
}
24 changes: 24 additions & 0 deletions Sources/BluetoothLinux/BNEP/BNEPConnectionFlag.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// BNEPConnectionFlag.swift
// BluetoothLinux
//

/// BNEP connection flags.
@frozen
public struct BNEPConnectionFlag: OptionSet, Equatable, Hashable, Sendable {

public let rawValue: UInt32

public init(rawValue: UInt32) {
self.rawValue = rawValue
}
}

public extension BNEPConnectionFlag {

/// The kernel should send the setup connection response.
///
/// When set, the setup connection request has already been received
/// and the kernel replies to it once the session is created.
static var setupResponse: BNEPConnectionFlag { BNEPConnectionFlag(rawValue: 1 << 0) }
}
19 changes: 19 additions & 0 deletions Sources/BluetoothLinux/BNEP/BNEPConnectionState.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// BNEPConnectionState.swift
// BluetoothLinux
//

/// BNEP connection state.
public enum BNEPConnectionState: UInt16, CaseIterable, Sendable {

case unknown = 0x00
case connected = 0x01
case open = 0x02
case bound = 0x03
case listening = 0x04
case connecting = 0x05
case connecting2 = 0x06
case config = 0x07
case disconnecting = 0x08
case closed = 0x09
}
37 changes: 37 additions & 0 deletions Sources/BluetoothLinux/BNEP/BNEPRole.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// BNEPRole.swift
// BluetoothLinux
//

/// BNEP service role.
///
/// The role of a device in a Bluetooth network encapsulation session,
/// identified by its service class UUID.
public enum BNEPRole: UInt16, CaseIterable, Sendable {

/// Personal Area Network User
case personalAreaNetworkUser = 0x1115

/// Network Access Point
case networkAccessPoint = 0x1116

/// Group Ad-hoc Network
case groupNetwork = 0x1117
}

// MARK: - CustomStringConvertible

extension BNEPRole: CustomStringConvertible, CustomDebugStringConvertible {

public var description: String {
switch self {
case .personalAreaNetworkUser: return "PANU"
case .networkAccessPoint: return "NAP"
case .groupNetwork: return "GN"
}
}

public var debugDescription: String {
description
}
}
97 changes: 97 additions & 0 deletions Sources/BluetoothLinux/BNEP/IOCTL/BNEPAddConnection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//
// BNEPAddConnection.swift
// BluetoothLinux
//

import Bluetooth
import SystemPackage
import Socket

public extension BNEPIO {

/// BNEP Add Connection
///
/// Bridges a connected L2CAP socket into a kernel network interface.
/// The kernel writes the name of the created interface back into the request.
struct AddConnection: IOControlValue {

@_alwaysEmitIntoClient
public static var id: BNEPIO { .addConnection }

@usableFromInline
internal private(set) var bytes: CInterop.BNEPConnectionAddRequest

@usableFromInline
internal init(_ bytes: CInterop.BNEPConnectionAddRequest) {
self.bytes = bytes
}

public init(
socket: SocketDescriptor,
flags: BNEPConnectionFlag = [],
role: BNEPRole,
device: String = ""
) {
self.init(CInterop.BNEPConnectionAddRequest(
socket: socket.rawValue,
flags: flags.rawValue,
role: role.rawValue,
device: device.deviceNameBytes)
)
}

@_alwaysEmitIntoClient
public mutating func withUnsafeMutablePointer<Result>(_ body: (UnsafeMutableRawPointer) throws -> (Result)) rethrows -> Result {
try Swift.withUnsafeMutableBytes(of: &bytes) { buffer in
try body(buffer.baseAddress!)
}
}
}
}

public extension BNEPIO.AddConnection {

@_alwaysEmitIntoClient
var socket: SocketDescriptor {
return .init(rawValue: bytes.socket)
}

@_alwaysEmitIntoClient
var flags: BNEPConnectionFlag {
return .init(rawValue: bytes.flags)
}

@_alwaysEmitIntoClient
var role: BNEPRole? {
return .init(rawValue: bytes.role)
}

/// Name of the network interface bridging the connection (e.g. `bnep0`).
var device: String {
withUnsafeBytes(of: bytes.device) { buffer in
String(decoding: buffer.prefix(while: { $0 != 0 }), as: UTF8.self)
}
}
}

// MARK: - File Descriptor

internal extension SocketDescriptor {

@usableFromInline
func bnepAddConnection(
socket: SocketDescriptor,
flags: BNEPConnectionFlag = [],
role: BNEPRole,
device: String = ""
) throws -> String {
var request = BNEPIO.AddConnection(
socket: socket,
flags: flags,
role: role,
device: device
)
try inputOutput(&request)
return request.device
}
}
Loading
Loading