diff --git a/packages/linux_can/lib/linux_can.dart b/packages/linux_can/lib/linux_can.dart index 1f2cf39..fba0774 100644 --- a/packages/linux_can/lib/linux_can.dart +++ b/packages/linux_can/lib/linux_can.dart @@ -1 +1,3 @@ export 'src/linux_can.dart'; +export 'src/data_classes.dart'; +export 'src/can_device.dart'; diff --git a/packages/linux_can/lib/src/can_device.dart b/packages/linux_can/lib/src/can_device.dart new file mode 100644 index 0000000..777829f --- /dev/null +++ b/packages/linux_can/lib/src/can_device.dart @@ -0,0 +1,337 @@ +import 'dart:async'; +import 'dart:ffi' as ffi; + +import 'package:_ardera_common_libc_bindings/_ardera_common_libc_bindings.dart'; +import 'package:_ardera_common_libc_bindings/epoll_event_loop.dart'; +import 'package:ffi/ffi.dart' as ffi; +import 'package:linux_can/src/data_classes.dart'; +import 'package:linux_can/src/platform_interface.dart'; + +/// CAN device +class CanDevice { + CanDevice({ + required PlatformInterface platformInterface, + required RtnetlinkSocket netlinkSocket, + required this.networkInterface, + }) : _platformInterface = platformInterface, + _netlinkSocket = netlinkSocket; + + final PlatformInterface _platformInterface; + final RtnetlinkSocket _netlinkSocket; + final NetworkInterface networkInterface; + + void _setCanInterfaceAttributes({ + bool? setUpDown, + CanBitTiming? bitTiming, + Map? ctrlModeFlags, + int? restartMs, + bool restart = false, + CanBitTiming? dataBitTiming, + int? termination, + }) { + return _netlinkSocket.setCanInterfaceAttributes( + networkInterface.index, + setUpDown: setUpDown, + bitTiming: bitTiming, + ctrlModeFlags: ctrlModeFlags, + restartMs: restartMs, + restart: restart, + dataBitTiming: dataBitTiming, + termination: termination, + ); + } + + /// TODO: Implement + bool get isUp { + /// TODO: Maybe cache these values? + /// They could change without our knowledge at any time though. + throw UnimplementedError(); + } + + set isUp(bool value) { + _setCanInterfaceAttributes(setUpDown: true); + } + + /// TODO: Implement + CanBitTiming get bitTiming { + throw UnimplementedError(); + } + + set bitTiming(CanBitTiming timing) { + /// TODO: Should we allow a way to force set these, + /// without checking isUp? + assert(!isUp); + + // TODO: Check value is accepted beforehand + _setCanInterfaceAttributes(bitTiming: bitTiming); + } + + set bitrate(int bitrate) { + // TODO: Check value is accepted beforehand + _setCanInterfaceAttributes( + bitTiming: CanBitTiming.bitrateOnly(bitrate: bitrate), + ); + } + + /// CAN hardware-dependent bit-timing constants + /// + /// Used for calculating and checking bit-timing parameters + /// + /// TODO: Implement + CanBitTimingLimits get bitTimingConstants { + // ref: https://elixir.bootlin.com/linux/latest/source/drivers/net/can/dev/netlink.c#L555 + throw UnimplementedError(); + } + + String get hardwareName => bitTimingConstants.hardwareName; + + /// CAN system clock frequency in Hz + /// TODO: Implement + int get clockFrequency { + // ref: https://elixir.bootlin.com/linux/latest/source/drivers/net/can/dev/netlink.c#L558 + throw UnimplementedError(); + } + + /// CAN bus state + /// TODO: Implement + CanState get state { + throw UnimplementedError(); + } + + /// TODO: Implement + Map get controllerMode { + throw UnimplementedError(); + } + + /// TODO: Implement + set controllerMode(Map flags) { + _setCanInterfaceAttributes(ctrlModeFlags: flags); + } + + /// TODO: Implement + int get restartDelayMillis { + throw UnimplementedError(); + } + + /// TODO: Implement + set restartDelayMillis(int millis) { + _setCanInterfaceAttributes(restartMs: millis); + } + + /// TODO: Implement + void restart() { + _setCanInterfaceAttributes(restart: true); + } + + /// TODO: Implement + CanBusErrorCounters get busErrorCounters { + throw UnimplementedError(); + } + + /// TODO: Implement + CanBitTiming get dataBitTiming { + throw UnimplementedError(); + } + + /// TODO: Implement + set dataBitTiming(CanBitTiming timing) { + assert(!isUp); + + // ref: https://elixir.bootlin.com/linux/latest/source/drivers/net/can/dev/netlink.c#L303 + + _setCanInterfaceAttributes(dataBitTiming: timing); + } + + /// TODO: Implement + CanBitTimingLimits get dataBitTimingConstants { + throw UnimplementedError(); + } + + /// TODO: Implement + int get termination { + throw UnimplementedError(); + } + + /// TODO: Implement + set termination(int termination) { + // not all values accepted + // TODO: Check value is accepted beforehand + // ref: https://elixir.bootlin.com/linux/latest/source/drivers/net/can/dev/netlink.c#L365 + + _setCanInterfaceAttributes(termination: termination); + } + + /// TODO: Implement + int get terminationConst { + throw UnimplementedError(); + } + + /// TODO: Implement + int get bitrateConst { + throw UnimplementedError(); + } + + /// TODO: Implement + int get dataBitRateConst { + throw UnimplementedError(); + } + + /// TODO: Implement + int get bitRateMax { + throw UnimplementedError(); + } + + // TODO: Implement TDC info + // ref: + // https://elixir.bootlin.com/linux/latest/source/drivers/net/can/dev/netlink.c#L467 + + // TODO: Implement supported controller modes + // ref: + // https://elixir.bootlin.com/linux/latest/source/drivers/net/can/dev/netlink.c#L520 + + // TODO: Implement device statistics + // ref: + // https://elixir.bootlin.com/linux/latest/source/drivers/net/can/dev/netlink.c#L614 + + CanSocket open() { + final fd = _platformInterface.createCanSocket(); + try { + _platformInterface.bind(fd, networkInterface.index); + + return CanSocket( + platformInterface: _platformInterface, + fd: fd, + networkInterface: networkInterface, + ); + } on Object { + _platformInterface.close(fd); + rethrow; + } + } +} + +class CanSocket { + CanSocket({ + required PlatformInterface platformInterface, + required int fd, + required this.networkInterface, + }) : _fd = fd, + _platformInterface = platformInterface; + + final PlatformInterface _platformInterface; + final int _fd; + final NetworkInterface networkInterface; + var _open = true; + + var _listening = false; + FdHandler? _fdListener; + ffi.Pointer? _fdHandlerBuffer; + + void write(CanFrame frame) { + assert(_open); + _platformInterface.write(_fd, frame); + } + + CanFrame? read() { + assert(_open); + return _platformInterface.read(_fd); + } + + Future close() async { + if (_listening) { + await _fdUnlisten(); + } + + assert(_open); + _platformInterface.close(_fd); + _open = false; + } + + static List? _handleFdReady(EpollIsolate isolate, int fd, Set flags, dynamic bufferAddr) { + assert(bufferAddr is int); + + final libc = isolate.libc; + + final buffer = ffi.Pointer.fromAddress(bufferAddr); + + final frames = []; + while (true) { + final frame = PlatformInterface.readStatic(libc, fd, buffer, ffi.sizeOf()); + if (frame != null) { + frames.add(frame); + } else { + break; + } + } + + return frames.isEmpty ? null : frames; + } + + Future _fdListen( + void Function(List?) onFrame, + void Function(Object error, StackTrace? stackTrace) onError, + ) async { + assert(_open); + assert(!_listening); + assert(_fdListener == null); + assert(_fdHandlerBuffer == null); + + _fdHandlerBuffer = ffi.calloc(); + + _fdListener = await _platformInterface.eventListener.add( + fd: _fd, + events: {EpollFlag.inReady}, + isolateCallback: _handleFdReady, + isolateCallbackContext: _fdHandlerBuffer!.address, + onValue: (value) { + assert(value is List?); + onFrame(value); + }, + onError: onError, + ); + + _listening = true; + } + + Future _fdUnlisten() async { + assert(_open); + assert(_listening); + assert(_fdListener != null); + assert(_fdHandlerBuffer != null); + + await _platformInterface.eventListener.delete( + listener: _fdListener!, + ); + + ffi.calloc.free(_fdHandlerBuffer!); + + _fdHandlerBuffer = null; + _fdListener = null; + _listening = false; + } + + Stream? _frames; + Stream get frames { + assert(_open); + + if (_frames == null) { + late StreamController controller; + + controller = StreamController.broadcast( + onListen: () { + _fdListen( + (frames) => frames?.forEach(controller.add), + controller.addError, + ); + }, + onCancel: () { + _fdUnlisten(); + }, + ); + + _frames = controller.stream; + } + + return _frames!; + } +} diff --git a/packages/linux_can/lib/src/data_classes.dart b/packages/linux_can/lib/src/data_classes.dart new file mode 100644 index 0000000..9d9413c --- /dev/null +++ b/packages/linux_can/lib/src/data_classes.dart @@ -0,0 +1,490 @@ +import 'package:_ardera_common_libc_bindings/_ardera_common_libc_bindings.dart'; +import 'package:quiver/collection.dart'; + +/// A linux network interface. +/// +/// Combination of an interface index and interface name. +/// +/// For example, `can0`, 5. +class NetworkInterface { + NetworkInterface({required this.index, required this.name}); + + final int index; + final String name; +} + +/// Defines CAN bit-timing values. +/// +/// For further reference, see: http://esd.cs.ucr.edu/webres/can20.pdf +class CanBitTiming { + const CanBitTiming({ + required this.bitrate, + required this.samplePoint, + required this.tq, + required this.propagationSegment, + required this.phaseSegment1, + required this.phaseSegment2, + required this.syncJumpWidth, + required this.bitratePrescaler, + }) : assert(0 < bitrate), + assert(0 <= samplePoint && samplePoint <= 1000), + assert(0 < tq), + assert(0 < propagationSegment), + assert(0 < phaseSegment1), + assert(0 < phaseSegment2), + assert(0 < syncJumpWidth), + assert(0 < bitratePrescaler); + + const CanBitTiming.bitrateOnly({required this.bitrate}) + : samplePoint = 0, + tq = 0, + propagationSegment = 0, + phaseSegment1 = 0, + phaseSegment2 = 0, + syncJumpWidth = 0, + bitratePrescaler = 0; + + /// Bit-rate in bits/second, in absence of resynchronization, + /// by an ideal transmitter. + final int bitrate; + + /// Sample point in one-tenth of a percent. + final int samplePoint; + + /// Time quanta (TQ) in nanoseconds. + /// + /// Other timings values in the struct are expressed as + /// multiples of this value. + final int tq; + + /// Propagation Segment in TQs. + /// + /// CAN 2.0 specification writes: + /// This part of the bit time is used to compensate for physical delay times + /// within the network. + /// It is twice the sum of the signal's propagation time on the bus line, the + /// input comparator delay, and the output driver delay. + final int propagationSegment; + + /// Phase buffer segment 1 in TQs. + /// + /// CAN 2.0 specification writes: + /// These Phase-Buffer-Segments are used to compensate for edge phase errors. + /// These segments can be lengthened or shortened by resynchronization. + final int phaseSegment1; + + /// Phase buffer segment 2 in TQs. + /// + /// CAN 2.0 specification writes: + /// These Phase-Buffer-Segments are used to compensate for edge phase errors. + /// These segments can be lengthened or shortened by resynchronization. + final int phaseSegment2; + + /// Synchronisation jump width in TQs. + final int syncJumpWidth; + + /// Bit-rate prescaler. + final int bitratePrescaler; +} + +/// CAN controller mode +/// +/// ref +/// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L95 +/// https://elixir.bootlin.com/linux/v6.3/source/drivers/net/can/dev/netlink.c#L240 +enum CanModeFlag { + /// Loopback mode. + /// + /// ref: + /// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L95 + loopback(CAN_CTRLMODE_LOOPBACK), + + /// Listen-only mode. + /// + /// ref: + /// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L96 + listenOnly(CAN_CTRLMODE_LISTENONLY), + + /// Triple sampling mode. + /// + /// ref: + /// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L97 + tripleSample(CAN_CTRLMODE_3_SAMPLES), + + /// One-Shot mode. + /// + /// ref: + /// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L98 + oneShot(CAN_CTRLMODE_ONE_SHOT), + + /// Bus-error reporting. + /// + /// ref: + /// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L99 + busErrorReporting(CAN_CTRLMODE_BERR_REPORTING), + + /// CAN Flexible Datarate (FD) mode. + /// + /// ref: + /// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L100 + flexibleDatarate(CAN_CTRLMODE_FD), + + /// Ignore missing CAN ACKs + /// + /// ref: + /// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L101 + presumeAck(CAN_CTRLMODE_PRESUME_ACK), + + /// CAN Flexible Datarate (FD) in non-ISO mode. + /// + /// ref: + /// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L102 + flexibleDatarateNonIso(CAN_CTRLMODE_FD_NON_ISO), + + /// Classic CAN data-length-code (DLC) option. + /// + /// ref: + /// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L103 + classicCanDlc(CAN_CTRLMODE_CC_LEN8_DLC), + + /// CAN transceiver automatically calculates TDCV + /// + /// (TDC = CAN FD Transmission Delay Compensation) + /// + /// ref: + /// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L104 + tdcAuto(CAN_CTRLMODE_TDC_AUTO), + + /// TDCV is manually set up by user + /// + /// (TDC = CAN FD Transmission Delay Compensation) + /// + /// ref: + /// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L105 + tdcManual(CAN_CTRLMODE_TDC_MANUAL); + + const CanModeFlag(this.value); + + final int value; +} + +class CanBitTimingLimits { + const CanBitTimingLimits({ + required this.hardwareName, + required this.timeSegment1Min, + required this.timeSegment1Max, + required this.timeSegment2Min, + required this.timeSegment2Max, + required this.synchronisationJumpWidth, + required this.bitRatePrescalerMin, + required this.bitRatePrescalerMax, + required this.bitRatePrescalerIncrement, + }); + + final String hardwareName; + final int timeSegment1Min; + final int timeSegment1Max; + final int timeSegment2Min; + final int timeSegment2Max; + final int synchronisationJumpWidth; + final int bitRatePrescalerMin; + final int bitRatePrescalerMax; + final int bitRatePrescalerIncrement; +} + +/// CAN operation and error states +/// +/// ref: +/// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/can/netlink.h#L66 +enum CanState { + /// RX/TX error count < 96 + errorActive(0), + + /// RX/TX error count < 128 + errorWarning(1), + + /// RX/TX error count < 256 + errorPassive(2), + + /// RX/TX error count >= 256 + busOff(3), + + /// Device is stopped + stopped(4), + + /// Device is sleeping + sleeping(5); + + const CanState(this.value); + + final int value; +} + +/// CAN bus error counters +/// +/// ref: +/// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/can/netlink.h#L79 +class CanBusErrorCounters { + const CanBusErrorCounters({ + required this.txErrors, + required this.rxErrors, + }) : assert(txErrors >= 0), + assert(rxErrors >= 0); + + final int txErrors; + final int rxErrors; +} + +/// CAN device statistics +class CanDeviceStats { + const CanDeviceStats({ + required this.busError, + required this.errorWarning, + required this.errorPassive, + required this.busOff, + required this.arbitrationLost, + required this.restarts, + }); + + /// Bus errors + final int busError; + + /// Changes to error warning state + final int errorWarning; + + /// Changes to error passive state + final int errorPassive; + + /// Changes to bus off state + final int busOff; + + /// Arbitration lost errors + final int arbitrationLost; + + /// CAN controller re-starts + final int restarts; +} + +/// CAN FD Transmission Delay Compensation parameters +/// +/// At high bit rates, the propagation delay from the TX pin to the RX +/// pin of the transceiver causes measurement errors: the sample point +/// on the RX pin might occur on the previous bit. +/// +/// To solve this issue, ISO 11898-1 introduces in section 11.3.3 +/// "Transmitter delay compensation" a SSP (Secondary Sample Point) +/// equal to the distance from the start of the bit time on the TX pin +/// to the actual measurement on the RX pin. +/// +/// This structure contains the parameters to calculate that SSP. +/// +/// -+----------- one bit ----------+-- TX pin +/// |<--- Sample Point --->| +/// +/// --+----------- one bit ----------+-- RX pin +/// |<-------- TDCV -------->| +/// |<------- TDCO ------->| +/// |<----------- Secondary Sample Point ---------->| +/// +/// To increase precision, contrary to the other bittiming parameters +/// which are measured in time quanta, the TDC parameters are measured +/// in clock periods (also referred as "minimum time quantum" in ISO +/// 11898-1). +/// +/// See: +/// https://elixir.bootlin.com/linux/v6.3/source/include/linux/can/bittiming.h#L20 +class CanTransmissionDelayCompensation { + const CanTransmissionDelayCompensation({ + required this.value, + required this.offset, + required this.window, + }); + + /// Transmission Delay Compensation Value. + /// + /// The time needed for the signal to propagate, i.e. the distance, + /// in clock periods, from the start of the bit on the TX pin to + /// when it is received on the RX pin. + /// + /// [value] depends on the controller mode: + /// - If [CanModeFlag.tdcAuto] is set: + /// The transceiver dynamically measures [value] for each + /// transmitteed CAN FD frame and the value provided here + /// should be ignored. + /// + /// - If [CanModeFlag.tdcManual] is set: + /// Use the fixed provided value. + final int value; + + /// Transmission Delay Compensation Offset. + /// + /// Offset value, in clock periods, defining the distance between + /// the start of the bit reception on the RX pin of the transceiver + /// and the SSP position such that SSP = @tdcv + @tdco. + final int offset; + + /// Transmission Delay Compensation Filter window. + /// + /// Defines the minimum value for the SSP position in clock periods. + /// + /// If the SSP position is less than [window], then no delay + /// compensations occur and the normal sampling point is used instead. + /// + /// The features is enabled if and only if [value] is set to zero + /// (automatic mode) and [window] is configured to a value + /// greater than [offset]. + final int window; +} + +/// CAN hardware-dependent constants for Transmission Delay Compensation. +/// +/// See: +/// https://elixir.bootlin.com/linux/v6.3/source/include/linux/can/bittiming.h#L84 +class CanTransmissionDelayCompensationLimits { + CanTransmissionDelayCompensationLimits({ + required this.valueMin, + required this.valueMax, + required this.offsetMin, + required this.offsetMax, + required this.windowMin, + required this.windowMax, + }); + + /// [CanTransmissionDelayCompensation.value] minimum value. + /// + /// If the controller does not support manual mode ([CanModeFlag.tdcManual]), + /// then this value is ignored. + final int valueMin; + + /// [CanTransmissionDelayCompensation.value] maximum value. + /// + /// If the controller does not support manual mode ([CanModeFlag.tdcManual]), + /// then this value is ignored. + final int valueMax; + + /// [CanTransmissionDelayCompensation.offset] minimum value. + final int offsetMin; + + /// [CanTransmissionDelayCompensation.offset] maximum value. + /// + /// Should not be zero. + final int offsetMax; + + /// [CanTransmissionDelayCompensation.window] minimum value. + /// + /// If [windowMax] is zero, this value should be ignored. + final int windowMin; + + /// [CanTransmissionDelayCompensation.window] maximum value. + /// + /// Should be set to zero if the controller does not support this feature. + final int windowMax; +} + +/// Classical CAN frame structure (aka CAN 2.0B) +abstract class CanFrame { + const CanFrame(); + + /// Data frame, + /// CAN 2.0B Standard Frame Format + factory CanFrame.standard({required int id, required List data}) { + return CanStandardDataFrame(id: id, data: data); + } + + /// Data frame, + /// CAN 2.0B Extended Frame Format + factory CanFrame.extended({required int id, required List data}) { + return CanExtendedDataFrame(id: id, data: data); + } + + /// Remote Frame / Remote Transmission Request, + /// CAN 2.0B Standard Frame Format + factory CanFrame.standardRemote({required int id}) { + return CanStandardRemoteFrame(id: id); + } + + /// Remote Frame / Remote Transmission Request, + /// CAN 2.0B Extended Frame Format + factory CanFrame.extendedRemote({required int id}) { + return CanExtendedRemoteFrame(id: id); + } + + /// Error Frame + factory CanFrame.error() { + return CanErrorFrame(); + } +} + +abstract class CanDataFrame extends CanFrame { + const CanDataFrame({required this.id, required this.data}) : assert(0 <= data.length && data.length <= 8); + + /// CAN ID of the frame. + final int id; + + final List data; +} + +class CanStandardDataFrame extends CanDataFrame { + const CanStandardDataFrame({required super.id, required super.data}) : assert(id & ~CAN_SFF_MASK == 0); + + @override + operator ==(Object other) { + return other is CanStandardDataFrame && other.id == id && listsEqual(other.data, data); + } + + @override + int get hashCode => Object.hash(id.hashCode, data.hashCode); +} + +class CanExtendedDataFrame extends CanDataFrame { + const CanExtendedDataFrame({required super.id, required super.data}) : assert(id & ~CAN_EFF_MASK == 0); + + @override + operator ==(Object other) { + return other is CanExtendedDataFrame && other.id == id && listsEqual(other.data, data); + } + + @override + int get hashCode => Object.hash(id.hashCode, data.hashCode); +} + +abstract class CanRemoteFrame extends CanFrame { + const CanRemoteFrame({required this.id}); + + /// CAN ID of the frame. + final int id; +} + +class CanStandardRemoteFrame extends CanRemoteFrame { + const CanStandardRemoteFrame({required super.id}) : assert(id & ~CAN_SFF_MASK == 0); + + @override + operator ==(Object other) { + return other is CanStandardRemoteFrame && other.id == id; + } + + @override + int get hashCode => id.hashCode; +} + +class CanExtendedRemoteFrame extends CanRemoteFrame { + const CanExtendedRemoteFrame({required super.id}) : assert(id & ~CAN_EFF_MASK == 0); + + @override + operator ==(Object other) { + return other is CanExtendedRemoteFrame && other.id == id; + } + + @override + int get hashCode => id.hashCode; +} + +class CanErrorFrame extends CanFrame { + /// The fact that we only have one instance of this means we don't need to implement + /// operator ==() and hashCode(). + const CanErrorFrame._(); + + factory CanErrorFrame() { + return const CanErrorFrame._(); + } +} diff --git a/packages/linux_can/lib/src/linux_can.dart b/packages/linux_can/lib/src/linux_can.dart index 6a388a9..c98717b 100644 --- a/packages/linux_can/lib/src/linux_can.dart +++ b/packages/linux_can/lib/src/linux_can.dart @@ -1,1380 +1,10 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:ffi'; - -import 'package:ffi/ffi.dart'; - -import 'package:_ardera_common_libc_bindings/_ardera_common_libc_bindings.dart'; -import 'package:_ardera_common_libc_bindings/linux_error.dart'; -import 'package:_ardera_common_libc_bindings/epoll_event_loop.dart'; - -/// A linux network interface. -/// -/// Combination of an interface index and interface name. -/// -/// For example, `can0`, 5. -class NetworkInterface { - NetworkInterface({required this.index, required this.name}); - - final int index; - final String name; -} - -/// Defines CAN bit-timing values. -/// -/// For further reference, see: http://esd.cs.ucr.edu/webres/can20.pdf -class CanBitTiming { - const CanBitTiming({ - required this.bitrate, - required this.samplePoint, - required this.tq, - required this.propagationSegment, - required this.phaseSegment1, - required this.phaseSegment2, - required this.syncJumpWidth, - required this.bitratePrescaler, - }) : assert(0 < bitrate), - assert(0 <= samplePoint && samplePoint <= 1000), - assert(0 < tq), - assert(0 < propagationSegment), - assert(0 < phaseSegment1), - assert(0 < phaseSegment2), - assert(0 < syncJumpWidth), - assert(0 < bitratePrescaler); - - const CanBitTiming.bitrateOnly({required this.bitrate}) - : samplePoint = 0, - tq = 0, - propagationSegment = 0, - phaseSegment1 = 0, - phaseSegment2 = 0, - syncJumpWidth = 0, - bitratePrescaler = 0; - - /// Bit-rate in bits/second, in absence of resynchronization, - /// by an ideal transmitter. - final int bitrate; - - /// Sample point in one-tenth of a percent. - final int samplePoint; - - /// Time quanta (TQ) in nanoseconds. - /// - /// Other timings values in the struct are expressed as - /// multiples of this value. - final int tq; - - /// Propagation Segment in TQs. - /// - /// CAN 2.0 specification writes: - /// This part of the bit time is used to compensate for physical delay times - /// within the network. - /// It is twice the sum of the signal's propagation time on the bus line, the - /// input comparator delay, and the output driver delay. - final int propagationSegment; - - /// Phase buffer segment 1 in TQs. - /// - /// CAN 2.0 specification writes: - /// These Phase-Buffer-Segments are used to compensate for edge phase errors. - /// These segments can be lengthened or shortened by resynchronization. - final int phaseSegment1; - - /// Phase buffer segment 2 in TQs. - /// - /// CAN 2.0 specification writes: - /// These Phase-Buffer-Segments are used to compensate for edge phase errors. - /// These segments can be lengthened or shortened by resynchronization. - final int phaseSegment2; - - /// Synchronisation jump width in TQs. - final int syncJumpWidth; - - /// Bit-rate prescaler. - final int bitratePrescaler; -} - -/// CAN controller mode -/// -/// ref -/// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L95 -/// https://elixir.bootlin.com/linux/v6.3/source/drivers/net/can/dev/netlink.c#L240 -enum CanModeFlag { - /// Loopback mode. - /// - /// ref: - /// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L95 - loopback(CAN_CTRLMODE_LOOPBACK), - - /// Listen-only mode. - /// - /// ref: - /// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L96 - listenOnly(CAN_CTRLMODE_LISTENONLY), - - /// Triple sampling mode. - /// - /// ref: - /// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L97 - tripleSample(CAN_CTRLMODE_3_SAMPLES), - - /// One-Shot mode. - /// - /// ref: - /// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L98 - oneShot(CAN_CTRLMODE_ONE_SHOT), - - /// Bus-error reporting. - /// - /// ref: - /// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L99 - busErrorReporting(CAN_CTRLMODE_BERR_REPORTING), - - /// CAN Flexible Datarate (FD) mode. - /// - /// ref: - /// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L100 - flexibleDatarate(CAN_CTRLMODE_FD), - - /// Ignore missing CAN ACKs - /// - /// ref: - /// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L101 - presumeAck(CAN_CTRLMODE_PRESUME_ACK), - - /// CAN Flexible Datarate (FD) in non-ISO mode. - /// - /// ref: - /// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L102 - flexibleDatarateNonIso(CAN_CTRLMODE_FD_NON_ISO), - - /// Classic CAN data-length-code (DLC) option. - /// - /// ref: - /// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L103 - classicCanDlc(CAN_CTRLMODE_CC_LEN8_DLC), - - /// CAN transceiver automatically calculates TDCV - /// - /// (TDC = CAN FD Transmission Delay Compensation) - /// - /// ref: - /// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L104 - tdcAuto(CAN_CTRLMODE_TDC_AUTO), - - /// TDCV is manually set up by user - /// - /// (TDC = CAN FD Transmission Delay Compensation) - /// - /// ref: - /// https://elixir.bootlin.com/linux/v6.3/source/include/uapi/linux/can/netlink.h#L105 - tdcManual(CAN_CTRLMODE_TDC_MANUAL); - - const CanModeFlag(this.value); - - final int value; -} - -class CanBitTimingLimits { - const CanBitTimingLimits({ - required this.hardwareName, - required this.timeSegment1Min, - required this.timeSegment1Max, - required this.timeSegment2Min, - required this.timeSegment2Max, - required this.synchronisationJumpWidth, - required this.bitRatePrescalerMin, - required this.bitRatePrescalerMax, - required this.bitRatePrescalerIncrement, - }); - - final String hardwareName; - final int timeSegment1Min; - final int timeSegment1Max; - final int timeSegment2Min; - final int timeSegment2Max; - final int synchronisationJumpWidth; - final int bitRatePrescalerMin; - final int bitRatePrescalerMax; - final int bitRatePrescalerIncrement; -} - -/// CAN operation and error states -/// -/// ref: -/// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/can/netlink.h#L66 -enum CanState { - /// RX/TX error count < 96 - errorActive(0), - - /// RX/TX error count < 128 - errorWarning(1), - - /// RX/TX error count < 256 - errorPassive(2), - - /// RX/TX error count >= 256 - busOff(3), - - /// Device is stopped - stopped(4), - - /// Device is sleeping - sleeping(5); - - const CanState(this.value); - - final int value; -} - -/// CAN bus error counters -/// -/// ref: -/// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/can/netlink.h#L79 -class CanBusErrorCounters { - const CanBusErrorCounters({ - required this.txErrors, - required this.rxErrors, - }) : assert(txErrors >= 0), - assert(rxErrors >= 0); - - final int txErrors; - final int rxErrors; -} - -/// CAN device statistics -class CanDeviceStats { - const CanDeviceStats({ - required this.busError, - required this.errorWarning, - required this.errorPassive, - required this.busOff, - required this.arbitrationLost, - required this.restarts, - }); - - /// Bus errors - final int busError; - - /// Changes to error warning state - final int errorWarning; - - /// Changes to error passive state - final int errorPassive; - - /// Changes to bus off state - final int busOff; - - /// Arbitration lost errors - final int arbitrationLost; - - /// CAN controller re-starts - final int restarts; -} - -/// CAN FD Transmission Delay Compensation parameters -/// -/// At high bit rates, the propagation delay from the TX pin to the RX -/// pin of the transceiver causes measurement errors: the sample point -/// on the RX pin might occur on the previous bit. -/// -/// To solve this issue, ISO 11898-1 introduces in section 11.3.3 -/// "Transmitter delay compensation" a SSP (Secondary Sample Point) -/// equal to the distance from the start of the bit time on the TX pin -/// to the actual measurement on the RX pin. -/// -/// This structure contains the parameters to calculate that SSP. -/// -/// -+----------- one bit ----------+-- TX pin -/// |<--- Sample Point --->| -/// -/// --+----------- one bit ----------+-- RX pin -/// |<-------- TDCV -------->| -/// |<------- TDCO ------->| -/// |<----------- Secondary Sample Point ---------->| -/// -/// To increase precision, contrary to the other bittiming parameters -/// which are measured in time quanta, the TDC parameters are measured -/// in clock periods (also referred as "minimum time quantum" in ISO -/// 11898-1). -/// -/// See: -/// https://elixir.bootlin.com/linux/v6.3/source/include/linux/can/bittiming.h#L20 -class CanTransmissionDelayCompensation { - const CanTransmissionDelayCompensation({ - required this.value, - required this.offset, - required this.window, - }); - - /// Transmission Delay Compensation Value. - /// - /// The time needed for the signal to propagate, i.e. the distance, - /// in clock periods, from the start of the bit on the TX pin to - /// when it is received on the RX pin. - /// - /// [value] depends on the controller mode: - /// - If [CanModeFlag.tdcAuto] is set: - /// The transceiver dynamically measures [value] for each - /// transmitteed CAN FD frame and the value provided here - /// should be ignored. - /// - /// - If [CanModeFlag.tdcManual] is set: - /// Use the fixed provided value. - final int value; - - /// Transmission Delay Compensation Offset. - /// - /// Offset value, in clock periods, defining the distance between - /// the start of the bit reception on the RX pin of the transceiver - /// and the SSP position such that SSP = @tdcv + @tdco. - final int offset; - - /// Transmission Delay Compensation Filter window. - /// - /// Defines the minimum value for the SSP position in clock periods. - /// - /// If the SSP position is less than [window], then no delay - /// compensations occur and the normal sampling point is used instead. - /// - /// The features is enabled if and only if [value] is set to zero - /// (automatic mode) and [window] is configured to a value - /// greater than [offset]. - final int window; -} - -/// CAN hardware-dependent constants for Transmission Delay Compensation. -/// -/// See: -/// https://elixir.bootlin.com/linux/v6.3/source/include/linux/can/bittiming.h#L84 -class CanTransmissionDelayCompensationLimits { - CanTransmissionDelayCompensationLimits({ - required this.valueMin, - required this.valueMax, - required this.offsetMin, - required this.offsetMax, - required this.windowMin, - required this.windowMax, - }); - - /// [CanTransmissionDelayCompensation.value] minimum value. - /// - /// If the controller does not support manual mode ([CanModeFlag.tdcManual]), - /// then this value is ignored. - final int valueMin; - - /// [CanTransmissionDelayCompensation.value] maximum value. - /// - /// If the controller does not support manual mode ([CanModeFlag.tdcManual]), - /// then this value is ignored. - final int valueMax; - - /// [CanTransmissionDelayCompensation.offset] minimum value. - final int offsetMin; - - /// [CanTransmissionDelayCompensation.offset] maximum value. - /// - /// Should not be zero. - final int offsetMax; - - /// [CanTransmissionDelayCompensation.window] minimum value. - /// - /// If [windowMax] is zero, this value should be ignored. - final int windowMin; - - /// [CanTransmissionDelayCompensation.window] maximum value. - /// - /// Should be set to zero if the controller does not support this feature. - final int windowMax; -} - -/// CAN device -class CanDevice { - CanDevice({ - required _PlatformInterface platformInterface, - required _RtnetlinkSocket netlinkSocket, - required this.networkInterface, - }) : _platformInterface = platformInterface, - _netlinkSocket = netlinkSocket; - - final _PlatformInterface _platformInterface; - final _RtnetlinkSocket _netlinkSocket; - final NetworkInterface networkInterface; - - void _setCanInterfaceAttributes({ - bool? setUpDown, - CanBitTiming? bitTiming, - Map? ctrlModeFlags, - int? restartMs, - bool restart = false, - CanBitTiming? dataBitTiming, - int? termination, - }) { - return _netlinkSocket.setCanInterfaceAttributes( - networkInterface.index, - setUpDown: setUpDown, - bitTiming: bitTiming, - ctrlModeFlags: ctrlModeFlags, - restartMs: restartMs, - restart: restart, - dataBitTiming: dataBitTiming, - termination: termination, - ); - } - - /// TODO: Implement - bool get isUp { - /// TODO: Maybe cache these values? - /// They could change without our knowledge at any time though. - throw UnimplementedError(); - } - - set isUp(bool value) { - _setCanInterfaceAttributes(setUpDown: true); - } - - /// TODO: Implement - CanBitTiming get bitTiming { - throw UnimplementedError(); - } - - set bitTiming(CanBitTiming timing) { - /// TODO: Should we allow a way to force set these, - /// without checking isUp? - assert(!isUp); - - // TODO: Check value is accepted beforehand - _setCanInterfaceAttributes(bitTiming: bitTiming); - } - - set bitrate(int bitrate) { - // TODO: Check value is accepted beforehand - _setCanInterfaceAttributes( - bitTiming: CanBitTiming.bitrateOnly(bitrate: bitrate), - ); - } - - /// CAN hardware-dependent bit-timing constants - /// - /// Used for calculating and checking bit-timing parameters - /// - /// TODO: Implement - CanBitTimingLimits get bitTimingConstants { - // ref: https://elixir.bootlin.com/linux/latest/source/drivers/net/can/dev/netlink.c#L555 - throw UnimplementedError(); - } - - String get hardwareName => bitTimingConstants.hardwareName; - - /// CAN system clock frequency in Hz - /// TODO: Implement - int get clockFrequency { - // ref: https://elixir.bootlin.com/linux/latest/source/drivers/net/can/dev/netlink.c#L558 - throw UnimplementedError(); - } - - /// CAN bus state - /// TODO: Implement - CanState get state { - throw UnimplementedError(); - } - - /// TODO: Implement - Map get controllerMode { - throw UnimplementedError(); - } - - /// TODO: Implement - set controllerMode(Map flags) { - _setCanInterfaceAttributes(ctrlModeFlags: flags); - } - - /// TODO: Implement - int get restartDelayMillis { - throw UnimplementedError(); - } - - /// TODO: Implement - set restartDelayMillis(int millis) { - _setCanInterfaceAttributes(restartMs: millis); - } - - /// TODO: Implement - void restart() { - _setCanInterfaceAttributes(restart: true); - } - - /// TODO: Implement - CanBusErrorCounters get busErrorCounters { - throw UnimplementedError(); - } - - /// TODO: Implement - CanBitTiming get dataBitTiming { - throw UnimplementedError(); - } - - /// TODO: Implement - set dataBitTiming(CanBitTiming timing) { - assert(!isUp); - - // ref: https://elixir.bootlin.com/linux/latest/source/drivers/net/can/dev/netlink.c#L303 - - _setCanInterfaceAttributes(dataBitTiming: timing); - } - - /// TODO: Implement - CanBitTimingLimits get dataBitTimingConstants { - throw UnimplementedError(); - } - - /// TODO: Implement - int get termination { - throw UnimplementedError(); - } - - /// TODO: Implement - set termination(int termination) { - // not all values accepted - // TODO: Check value is accepted beforehand - // ref: https://elixir.bootlin.com/linux/latest/source/drivers/net/can/dev/netlink.c#L365 - - _setCanInterfaceAttributes(termination: termination); - } - - /// TODO: Implement - int get terminationConst { - throw UnimplementedError(); - } - - /// TODO: Implement - int get bitrateConst { - throw UnimplementedError(); - } - - /// TODO: Implement - int get dataBitRateConst { - throw UnimplementedError(); - } - - /// TODO: Implement - int get bitRateMax { - throw UnimplementedError(); - } - - // TODO: Implement TDC info - // ref: - // https://elixir.bootlin.com/linux/latest/source/drivers/net/can/dev/netlink.c#L467 - - // TODO: Implement supported controller modes - // ref: - // https://elixir.bootlin.com/linux/latest/source/drivers/net/can/dev/netlink.c#L520 - - // TODO: Implement device statistics - // ref: - // https://elixir.bootlin.com/linux/latest/source/drivers/net/can/dev/netlink.c#L614 - - CanSocket open() { - final fd = _platformInterface.createCanSocket(); - try { - _platformInterface.bind(fd, networkInterface.index); - - return CanSocket( - platformInterface: _platformInterface, - fd: fd, - networkInterface: networkInterface, - ); - } on Object { - _platformInterface.close(fd); - rethrow; - } - } -} - -/// Classical CAN frame structure (aka CAN 2.0B) -abstract class CanFrame { - const CanFrame(); - - /// Data frame, - /// CAN 2.0B Standard Frame Format - factory CanFrame.standard({required int id, required List data}) { - return CanStandardDataFrame(id: id, data: data); - } - - /// Data frame, - /// CAN 2.0B Extended Frame Format - factory CanFrame.extended({required int id, required List data}) { - return CanExtendedDataFrame(id: id, data: data); - } - - /// Remote Frame / Remote Transmission Request, - /// CAN 2.0B Standard Frame Format - factory CanFrame.standardRemote({required int id}) { - return CanStandardRemoteFrame(id: id); - } - - /// Remote Frame / Remote Transmission Request, - /// CAN 2.0B Extended Frame Format - factory CanFrame.extendedRemote({required int id}) { - return CanExtendedRemoteFrame(id: id); - } - - /// Error Frame - factory CanFrame.error() { - return CanErrorFrame(); - } -} - -abstract class CanDataFrame extends CanFrame { - const CanDataFrame({required this.id, required this.data}) : assert(0 <= data.length && data.length <= 8); - - /// CAN ID of the frame. - final int id; - - final List data; -} - -class CanStandardDataFrame extends CanDataFrame { - const CanStandardDataFrame({required super.id, required super.data}) : assert(id & ~CAN_SFF_MASK == 0); -} - -class CanExtendedDataFrame extends CanDataFrame { - const CanExtendedDataFrame({required super.id, required super.data}) : assert(id & ~CAN_EFF_MASK == 0); -} - -abstract class CanRemoteFrame extends CanFrame { - const CanRemoteFrame({required this.id}); - - /// CAN ID of the frame. - final int id; -} - -class CanStandardRemoteFrame extends CanRemoteFrame { - const CanStandardRemoteFrame({required super.id}) : assert(id & ~CAN_SFF_MASK == 0); -} - -class CanExtendedRemoteFrame extends CanRemoteFrame { - const CanExtendedRemoteFrame({required super.id}) : assert(id & ~CAN_EFF_MASK == 0); -} - -class CanErrorFrame extends CanFrame { - const CanErrorFrame(); -} - -class CanSocket { - CanSocket({ - required _PlatformInterface platformInterface, - required int fd, - required this.networkInterface, - }) : _fd = fd, - _platformInterface = platformInterface; - - final _PlatformInterface _platformInterface; - final int _fd; - final NetworkInterface networkInterface; - var _open = true; - - var _listening = false; - FdHandler? _fdListener; - Pointer? _fdHandlerBuffer; - - void write(CanFrame frame) { - assert(_open); - _platformInterface.write(_fd, frame); - } - - CanFrame? read() { - assert(_open); - return _platformInterface.read(_fd); - } - - Future close() async { - if (_listening) { - await _fdUnlisten(); - } - - assert(_open); - _platformInterface.close(_fd); - _open = false; - } - - static List? _handleFdReady(EpollIsolate isolate, int fd, Set flags, int bufferAddr) { - final libc = isolate.libc; - - final buffer = Pointer.fromAddress(bufferAddr); - - final frames = []; - while (true) { - final frame = _PlatformInterface.readStatic(libc, fd, buffer, sizeOf()); - if (frame != null) { - frames.add(frame); - } else { - break; - } - } - - return frames.isEmpty ? null : frames; - } - - Future _fdListen( - void Function(List?) onFrame, - void Function(Object error, StackTrace? stackTrace) onError, - ) async { - assert(_open); - assert(!_listening); - assert(_fdListener == null); - assert(_fdHandlerBuffer == null); - - _fdHandlerBuffer = calloc(); - - _fdListener = await _platformInterface.eventListener.add?>( - fd: _fd, - events: {EpollFlag.inReady}, - isolateCallback: _handleFdReady, - isolateCallbackContext: _fdHandlerBuffer!.address, - onValue: onFrame, - onError: onError, - ); - - _listening = true; - } - - Future _fdUnlisten() async { - assert(_open); - assert(_listening); - assert(_fdListener != null); - assert(_fdHandlerBuffer != null); - - await _platformInterface.eventListener.delete( - listener: _fdListener!, - ); - - calloc.free(_fdHandlerBuffer!); - - _fdHandlerBuffer = null; - _fdListener = null; - _listening = false; - } - - Stream? _frames; - Stream get frames { - assert(_open); - - if (_frames == null) { - late StreamController controller; - - controller = StreamController.broadcast( - onListen: () { - _fdListen( - (frames) => frames?.forEach(controller.add), - controller.addError, - ); - }, - onCancel: () { - _fdUnlisten(); - }, - ); - - _frames = controller.stream; - } - - return _frames!; - } -} - -void _writeBytesToArray(List bytes, int length, void Function(int index, int value) setElement) { - bytes.take(length).toList().asMap().forEach(setElement); -} - -void _writeStringToArrayHelper( - String str, - int length, - void Function(int index, int value) setElement, { - Encoding codec = utf8, -}) { - final untruncatedBytes = List.of(codec.encode(str))..addAll(List.filled(length, 0)); - - untruncatedBytes.take(length).toList().asMap().forEach(setElement); -} - -class _RtnetlinkSocket { - _RtnetlinkSocket({required this.fd, required this.platformInterface}); - - final int fd; - final _PlatformInterface platformInterface; - - void setCanInterfaceAttributes( - int interfaceIndex, { - bool? setUpDown, - CanBitTiming? bitTiming, - Map? ctrlModeFlags, - int? restartMs, - bool restart = false, - CanBitTiming? dataBitTiming, - int? termination, - }) { - return platformInterface.setCanInterfaceAttributes( - fd, - interfaceIndex, - setUpDown: setUpDown, - bitTiming: bitTiming, - ctrlModeFlags: ctrlModeFlags, - restartMs: restartMs, - restart: restart, - dataBitTiming: dataBitTiming, - termination: termination, - ); - } -} - -class _PlatformInterface { - final LibC libc; - - late final _RtnetlinkSocket _rtnetlinkSocket = _RtnetlinkSocket( - fd: openRtNetlinkSocket(), - platformInterface: this, - ); - - late final eventListener = EpollEventLoop(libc); - - static const _bufferSize = 16384; - final _buffer = calloc.allocate(_bufferSize); - - _PlatformInterface() : libc = LibC(DynamicLibrary.open('libc.so.6')); - - int getInterfaceIndex(String interfaceName) { - final native = interfaceName.toNativeUtf8(); - - try { - final index = libc.if_nametoindex(native.cast()); - - if (index == 0) { - throw LinuxError( - 'Could not query interface index for interface name "$interfaceName".', 'if_nametoindex', libc.errno); - } - - return index; - } finally { - malloc.free(native); - } - } - - List getNetworkInterfaces() { - final nameindex = libc.if_nameindex1(); - - try { - final interfaces = []; - for (var offset = 0; nameindex.elementAt(offset).ref.if_index != 0; offset++) { - final element = nameindex.elementAt(offset); - - interfaces.add( - NetworkInterface( - index: element.ref.if_index, - name: element.ref.if_name.toString(), - ), - ); - } - - return interfaces; - } finally { - libc.if_freenameindex(nameindex); - } - } - - List getCanDevices() { - return [ - for (final interface in getNetworkInterfaces()) - if (interface.name.startsWith('can')) - CanDevice( - platformInterface: this, - netlinkSocket: _rtnetlinkSocket, - networkInterface: interface, - ), - ]; - } - - int createCanSocket() { - final socket = libc.socket(PF_CAN, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, CAN_RAW); - if (socket < 0) { - throw LinuxError('Could not create CAN socket.', 'socket', libc.errno); - } - - return socket; - } - - int getCanInterfaceMTU(int fd, String interfaceName) { - final req = calloc(); - - _writeStringToArrayHelper(interfaceName, IF_NAMESIZE, (index, byte) => req.ref.ifr_name[index] = byte); - - req.ref.ifr_ifindex = 0; - - final ok = libc.ioctlPtr(fd, SIOCGIFMTU, req); - if (ok < 0) { - calloc.free(req); - throw LinuxError('Could not get CAN interface MTU.', 'ioctl', libc.errno); - } - - final mtu = req.ref.ifr_mtu; - - calloc.free(req); - - return mtu; - } - - bool isFlexibleDatarateCapable(int fd, String interfaceName) { - return getCanInterfaceMTU(fd, interfaceName) == CANFD_MTU; - } - - void bind(int fd, int interfaceIndex) { - final addr = calloc(); - try { - addr.ref.can_family = AF_CAN; - addr.ref.can_ifindex = interfaceIndex; - - final ok = libc.bind(fd, addr.cast(), sizeOf()); - if (ok < 0) { - throw LinuxError('Couldn\'t bind socket to CAN interface.', 'bind', libc.errno); - } - } finally { - calloc.free(addr); - } - } - - void close(int fd) { - final ok = libc.close(fd); - if (ok < 0) { - throw LinuxError('Could not close CAN socket fd.', 'close', libc.errno); - } - } - - void write(int fd, CanFrame frame) { - final nativeFrame = calloc(); - - if (frame is CanDataFrame) { - nativeFrame.ref.can_id = frame.id; - if (frame is CanExtendedDataFrame) { - nativeFrame.ref.can_id |= CAN_EFF_FLAG; - } - - assert(frame.data.length <= 8); - - nativeFrame.ref.len = frame.data.length; - _writeBytesToArray( - frame.data, - 8, - (index, value) => nativeFrame.ref.data[index] = value, - ); - } else if (frame is CanRemoteFrame) { - nativeFrame.ref.can_id = frame.id; - if (frame is CanExtendedRemoteFrame) { - nativeFrame.ref.can_id |= CAN_EFF_FLAG; - } - nativeFrame.ref.can_id |= CAN_RTR_FLAG; - - nativeFrame.ref.len = 0; - } else if (frame is CanErrorFrame) { - nativeFrame.ref.can_id |= CAN_ERR_FLAG; - - nativeFrame.ref.len = 0; - } - - final ok = libc.write(fd, nativeFrame.cast(), sizeOf()); - - calloc.free(nativeFrame); - - if (ok < 0) { - throw LinuxError('Couldn\'t write CAN frame to socket.', 'write', libc.errno); - } else if (ok != CAN_MTU) { - throw LinuxError('Incomplete write.', 'write'); - } - } - - static CanFrame canFrameFromNative(can_frame native) { - final eff = native.can_id & CAN_EFF_FLAG != 0; - final rtr = native.can_id & CAN_RTR_FLAG != 0; - final err = native.can_id & CAN_ERR_FLAG != 0; - - if (err) { - return CanFrame.error(); - } else if (eff) { - final id = native.can_id & CAN_EFF_MASK; - - if (rtr) { - return CanFrame.extendedRemote(id: id); - } else { - return CanFrame.extended( - id: id, - data: List.generate( - native.len, - (index) => native.data[index], - growable: false, - ), - ); - } - } else { - final id = native.can_id & CAN_SFF_MASK; - - if (rtr) { - return CanFrame.standardRemote(id: id); - } else { - return CanFrame.standard( - id: id, - data: List.generate( - native.len, - (index) => native.data[index], - growable: false, - ), - ); - } - } - } - - static CanFrame? readStatic(LibC libc, int fd, Pointer buffer, int bufferSize) { - assert(bufferSize >= sizeOf()); - assert(CAN_MTU == sizeOf()); - - final ok = libc.read(fd, buffer, sizeOf()); - if (ok < 0 && libc.errno == EAGAIN) { - // no frame available right now. - return null; - } else if (ok < 0) { - throw LinuxError('Could not read CAN frame.', 'read', libc.errno); - } - - if (ok != CAN_MTU) { - throw LinuxError('Malformed CAN frame. Expected received frame to be $CAN_MTU bytes large, was: $ok.', 'read'); - } - - return canFrameFromNative(buffer.cast().ref); - } - - CanFrame? read(int fd) { - return readStatic(libc, fd, _buffer, _bufferSize); - } - - /// Opens an rtnetlink socket for kernel network interface manipulation. - /// - /// See: https://man7.org/linux/man-pages/man7/rtnetlink.7.html - int openRtNetlinkSocket() { - final fd = libc.socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); - if (fd < 0) { - throw LinuxError('Couldn\'t open netlink socket.', 'socket', libc.errno); - } - - try { - final sndbuf = malloc(); - final rcvbuf = malloc(); - - try { - sndbuf.value = 32768; - rcvbuf.value = 32768; - - var ok = libc.setsockopt(fd, SOL_SOCKET, SO_SNDBUF, sndbuf.cast(), sizeOf()); - if (ok < 0) { - throw LinuxError('Couldn\'t set netlink socket sndbuf size.', 'setsockopt', libc.errno); - } - - libc.setsockopt(fd, SOL_SOCKET, SO_RCVBUF, rcvbuf.cast(), sizeOf()); - if (ok < 0) { - throw LinuxError('Couldn\'t set netlink socket rcvbuf size.', 'setsockopt', libc.errno); - } - } finally { - malloc.free(sndbuf); - malloc.free(rcvbuf); - } - - final local = calloc(); - - try { - local.ref.nl_family = AF_NETLINK; - local.ref.nl_groups = 0; - - var ok = libc.bind(fd, local.cast(), sizeOf()); - if (ok < 0) { - throw LinuxError('Could\'t bind netlink socket.', 'bind', libc.errno); - } - - final addrLen = calloc(); - - try { - addrLen.value = sizeOf(); - - ok = libc.getsockname(fd, local.cast(), addrLen); - if (ok < 0) { - throw LinuxError('Error', 'getsockname', libc.errno); - } - - if (addrLen.value != sizeOf()) { - throw StateError('Invalid address length: ${addrLen.value}, should be: ${sizeOf()}'); - } - - if (local.ref.nl_family != AF_NETLINK) { - throw StateError('Invalid address family ${local.ref.nl_family}, should be: $AF_NETLINK'); - } - } finally { - calloc.free(addrLen); - } - } finally { - calloc.free(local); - } - } on Object { - libc.close(fd); - rethrow; - } - - return fd; - } - - void setCanInterfaceAttributes( - int netlinkFd, - int interfaceIndex, { - bool? setUpDown, - CanBitTiming? bitTiming, - Map? ctrlModeFlags, - int? restartMs, - bool restart = false, - CanBitTiming? dataBitTiming, - int? termination, - }) { - final maxLength = NLMSG_SPACE(sizeOf() + 2048); - - final nl = calloc.allocate(maxLength); - - try { - nl.ref.nlmsg_len = NLMSG_LENGTH(sizeOf()); - nl.ref.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; - nl.ref.nlmsg_type = RTM_NEWLINK; - - final ifinfo = NLMSG_DATA(nl); - - ifinfo.ref.ifi_family = 0; - ifinfo.ref.ifi_index = interfaceIndex; - ifinfo.ref.ifi_change = 0; - - if (setUpDown == true) { - ifinfo.ref.ifi_change |= IFF_UP; - ifinfo.ref.ifi_flags &= ~IFF_UP; - } else if (setUpDown == false) { - ifinfo.ref.ifi_change |= IFF_UP; - ifinfo.ref.ifi_flags |= IFF_UP; - } - - Pointer allocateAttr(int type, int lengthInBytes) { - final len = RTA_LENGTH(lengthInBytes); - if (NLMSG_SPACE(NLMSG_ALIGN(nl.ref.nlmsg_len) + RTA_ALIGN(len)) > maxLength) { - throw StateError('rtnetlink message exceeded maximum size of $maxLength'); - } - - final rta = NLMSG_TAIL(nl); - - rta.ref.rta_type = type; - rta.ref.rta_len = len; - - final data = RTA_DATA(rta); - - nl.ref.nlmsg_len = NLMSG_ALIGN(nl.ref.nlmsg_len) + RTA_ALIGN(len); - - return data; - } - - void addUint32Attr(int type, int value) { - final data = allocateAttr(type, sizeOf()); - data.value = value; - } - - void addStringAttr(int type, String str) { - final encoded = utf8.encode(str); - - final data = allocateAttr(type, encoded.length); - data.asTypedList(encoded.length).setAll(0, encoded); - } - - void addVoidAttr(int type) { - allocateAttr(type, 0); - } - - void addSection(int type, void Function() section) { - final sectionAttr = NLMSG_TAIL(nl); - - addVoidAttr(type); - - section(); - - sectionAttr.ref.rta_len = NLMSG_TAIL(nl).address - sectionAttr.address; - } - - addSection(IFLA_LINKINFO, () { - addStringAttr(IFLA_INFO_KIND, 'can'); - - addSection(IFLA_INFO_DATA, () { - if (restartMs != null) { - addUint32Attr(IFLA_CAN_RESTART_MS, restartMs); - } - - if (restart) { - addUint32Attr(IFLA_CAN_RESTART, 1); - } - - if (bitTiming != null) { - final bittimingPtr = allocateAttr(IFLA_CAN_BITTIMING, sizeOf()); - - bittimingPtr.ref.bitrate = bitTiming.bitrate; - bittimingPtr.ref.sample_point = bitTiming.samplePoint; - bittimingPtr.ref.tq = bitTiming.tq; - bittimingPtr.ref.prop_seg = bitTiming.propagationSegment; - bittimingPtr.ref.phase_seg1 = bitTiming.phaseSegment1; - bittimingPtr.ref.phase_seg2 = bitTiming.phaseSegment2; - bittimingPtr.ref.sjw = bitTiming.syncJumpWidth; - bittimingPtr.ref.brp = bitTiming.bitratePrescaler; - } - - if (ctrlModeFlags != null) { - final ctrlmodePtr = allocateAttr(IFLA_CAN_CTRLMODE, sizeOf()); - - for (final flag in ctrlModeFlags.keys) { - ctrlmodePtr.ref.mask |= flag.value; - - if (ctrlModeFlags[flag]!) { - ctrlmodePtr.ref.flags |= flag.value; - } - } - } - - if (dataBitTiming != null) { - final bittimingPtr = allocateAttr(IFLA_CAN_DATA_BITTIMING, sizeOf()); - - bittimingPtr.ref.bitrate = dataBitTiming.bitrate; - bittimingPtr.ref.sample_point = dataBitTiming.samplePoint; - bittimingPtr.ref.tq = dataBitTiming.tq; - bittimingPtr.ref.prop_seg = dataBitTiming.propagationSegment; - bittimingPtr.ref.phase_seg1 = dataBitTiming.phaseSegment1; - bittimingPtr.ref.phase_seg2 = dataBitTiming.phaseSegment2; - bittimingPtr.ref.sjw = dataBitTiming.syncJumpWidth; - bittimingPtr.ref.brp = dataBitTiming.bitratePrescaler; - } - - if (termination != null) { - final terminationPtr = allocateAttr(IFLA_CAN_TERMINATION, sizeOf()); - - terminationPtr.value = termination; - } - }); - }); - - final nladdr = calloc(); - - nladdr.ref.nl_family = AF_NETLINK; - nladdr.ref.nl_pid = 0; - nladdr.ref.nl_groups = 0; - - final iov = calloc(); - - iov.ref.iov_base = nl.cast(); - iov.ref.iov_len = nl.ref.nlmsg_len; - - final msg = calloc(); - - msg.ref.msg_name = nladdr.cast(); - msg.ref.msg_namelen = sizeOf(); - msg.ref.msg_iov = iov; - msg.ref.msg_iovlen = 1; - - const bufLen = 16384; - final buf = calloc(bufLen); - - // Could be unneccessary - nl.ref.nlmsg_seq = 0; - nl.ref.nlmsg_flags |= NLM_F_ACK; - - try { - var ok = libc.sendmsg(netlinkFd, msg, 0); - if (ok < 0) { - throw LinuxError('Error sending message to rtnetlink.', 'sendmsg', libc.errno); - } - - iov.ref.iov_base = buf.cast(); - while (true) { - iov.ref.iov_len = bufLen; - - ok = libc.recvmsg(netlinkFd, msg, 0); - if (ok < 0) { - throw LinuxError('Could not receive message from rtnetlink.', 'recvmsg', libc.errno); - } - - var bytesRemaining = ok; - - var nh = buf.cast(); - while (NLMSG_OK(nh, bytesRemaining)) { - if (nh.ref.nlmsg_type == NLMSG_DONE) { - // done processing - return; - } - - if (nh.ref.nlmsg_type == NLMSG_ERROR) { - // error - final err = NLMSG_DATA(nh); - if (nh.ref.nlmsg_len < NLMSG_SPACE(sizeOf())) { - throw LinuxError('Error message received from rtnetlink was truncated.'); - } - - if (err.ref.error < 0) { - throw LinuxError('RTNETLINK answers', null, -err.ref.error); - } else { - return; - } - } - - final tuple = NLMSG_NEXT(nh, bytesRemaining); - nh = tuple.item1; - bytesRemaining = tuple.item2; - } - - if (bytesRemaining > 0) { - throw LinuxError('Message received from rtnetlink was truncated.'); - } - } - } finally { - calloc.free(nladdr); - calloc.free(iov); - calloc.free(msg); - calloc.free(buf); - } - } finally { - calloc.free(nl); - } - } - - void sendDumpRequest(int netlinkFd, int interfaceIndex, int family, int type) { - final nl = calloc.allocate(NLMSG_SPACE(sizeOf())); - - nl.ref.nlmsg_len = NLMSG_LENGTH(sizeOf()); - nl.ref.nlmsg_type = type; - nl.ref.nlmsg_flags = NLM_F_REQUEST; - nl.ref.nlmsg_pid = 0; - nl.ref.nlmsg_seq = 0; - - final ifi = NLMSG_DATA(nl); - - ifi.ref.ifi_family = family; - ifi.ref.ifi_index = interfaceIndex; - - try { - final ok = libc.send(netlinkFd, nl.cast(), nl.ref.nlmsg_len, 0); - - if (ok < 0) { - throw LinuxError('Could not send info request to RTNETLINK.', 'send', libc.errno); - } - } finally { - calloc.free(nl); - } - } -} +import 'package:linux_can/src/can_device.dart'; +import 'package:linux_can/src/platform_interface.dart'; class LinuxCan { LinuxCan._(); - final _PlatformInterface _interface = _PlatformInterface(); + final PlatformInterface _interface = PlatformInterface(); static final LinuxCan instance = LinuxCan._(); diff --git a/packages/linux_can/lib/src/platform_interface.dart b/packages/linux_can/lib/src/platform_interface.dart new file mode 100644 index 0000000..93600d1 --- /dev/null +++ b/packages/linux_can/lib/src/platform_interface.dart @@ -0,0 +1,627 @@ +import 'dart:convert'; +import 'dart:ffi' as ffi; + +import 'package:_ardera_common_libc_bindings/_ardera_common_libc_bindings.dart'; +import 'package:_ardera_common_libc_bindings/epoll_event_loop.dart'; +import 'package:_ardera_common_libc_bindings/linux_error.dart'; +import 'package:ffi/ffi.dart' as ffi; +import 'package:linux_can/linux_can.dart'; + +void _writeBytesToArray(List bytes, int length, void Function(int index, int value) setElement) { + bytes.take(length).toList().asMap().forEach(setElement); +} + +void _writeStringToArrayHelper( + String str, + int length, + void Function(int index, int value) setElement, { + Encoding codec = utf8, +}) { + final untruncatedBytes = List.of(codec.encode(str))..addAll(List.filled(length, 0)); + + untruncatedBytes.take(length).toList().asMap().forEach(setElement); +} + +class RtnetlinkSocket { + RtnetlinkSocket({required this.fd, required this.platformInterface}); + + final int fd; + final PlatformInterface platformInterface; + + void setCanInterfaceAttributes( + int interfaceIndex, { + bool? setUpDown, + CanBitTiming? bitTiming, + Map? ctrlModeFlags, + int? restartMs, + bool restart = false, + CanBitTiming? dataBitTiming, + int? termination, + }) { + return platformInterface.setCanInterfaceAttributes( + fd, + interfaceIndex, + setUpDown: setUpDown, + bitTiming: bitTiming, + ctrlModeFlags: ctrlModeFlags, + restartMs: restartMs, + restart: restart, + dataBitTiming: dataBitTiming, + termination: termination, + ); + } +} + +class PlatformInterface { + final LibC libc; + + late final RtnetlinkSocket rtnetlinkSocket = RtnetlinkSocket( + fd: openRtNetlinkSocket(), + platformInterface: this, + ); + + late final eventListener = EpollEventLoop(libc); + + static const _bufferSize = 16384; + final _buffer = ffi.calloc.allocate(_bufferSize); + + final openFds = {}; + + PlatformInterface() : libc = LibC(ffi.DynamicLibrary.open('libc.so.6')); + + int getInterfaceIndex(String interfaceName) { + final native = interfaceName.toNativeUtf8(); + + try { + final index = libc.if_nametoindex(native.cast()); + + if (index == 0) { + throw LinuxError( + 'Could not query interface index for interface name "$interfaceName".', 'if_nametoindex', libc.errno); + } + + return index; + } finally { + ffi.malloc.free(native); + } + } + + List getNetworkInterfaces() { + final nameindex = libc.if_nameindex1(); + + try { + final interfaces = []; + for (var offset = 0; nameindex.elementAt(offset).ref.if_index != 0; offset++) { + final element = nameindex.elementAt(offset); + + interfaces.add( + NetworkInterface( + index: element.ref.if_index, + name: element.ref.if_name.cast().toDartString(), + ), + ); + } + + return interfaces; + } finally { + libc.if_freenameindex(nameindex); + } + } + + List getCanDevices() { + return [ + for (final interface in getNetworkInterfaces()) + if (interface.name.startsWith('can')) + CanDevice( + platformInterface: this, + netlinkSocket: rtnetlinkSocket, + networkInterface: interface, + ), + ]; + } + + int retry(int Function() syscall) { + late int result; + do { + result = syscall(); + } while ((result < 0) && libc.errno == EAGAIN); + + return result; + } + + int createCanSocket() { + final socket = retry(() => libc.socket(PF_CAN, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, CAN_RAW)); + if (socket < 0) { + throw LinuxError('Could not create CAN socket.', 'socket', libc.errno); + } + + openFds.add(socket); + + return socket; + } + + int getCanInterfaceMTU(int fd, String interfaceName) { + assert(openFds.contains(fd)); + + final req = ffi.calloc(); + + _writeStringToArrayHelper(interfaceName, IF_NAMESIZE, (index, byte) => req.ref.ifr_name[index] = byte); + + req.ref.ifr_ifindex = 0; + + final ok = libc.ioctlPtr(fd, SIOCGIFMTU, req); + if (ok < 0) { + ffi.calloc.free(req); + throw LinuxError('Could not get CAN interface MTU.', 'ioctl', libc.errno); + } + + final mtu = req.ref.ifr_mtu; + + ffi.calloc.free(req); + + return mtu; + } + + bool isFlexibleDatarateCapable(int fd, String interfaceName) { + assert(openFds.contains(fd)); + return getCanInterfaceMTU(fd, interfaceName) == CANFD_MTU; + } + + void bind(int fd, int interfaceIndex) { + assert(openFds.contains(fd)); + + final addr = ffi.calloc(); + try { + addr.ref.can_family = AF_CAN; + addr.ref.can_ifindex = interfaceIndex; + + final ok = libc.bind(fd, addr.cast(), ffi.sizeOf()); + if (ok < 0) { + throw LinuxError('Couldn\'t bind socket to CAN interface.', 'bind', libc.errno); + } + } finally { + ffi.calloc.free(addr); + } + } + + void close(int fd) { + assert(openFds.contains(fd)); + + final ok = libc.close(fd); + + openFds.remove(fd); + + if (ok < 0) { + throw LinuxError('Could not close CAN socket fd.', 'close', libc.errno); + } + } + + void write(int fd, CanFrame frame) { + assert(openFds.contains(fd)); + + final nativeFrame = ffi.calloc(); + + if (frame is CanDataFrame) { + nativeFrame.ref.can_id = frame.id; + if (frame is CanExtendedDataFrame) { + nativeFrame.ref.can_id |= CAN_EFF_FLAG; + } + + assert(frame.data.length <= 8); + + nativeFrame.ref.len = frame.data.length; + _writeBytesToArray( + frame.data, + 8, + (index, value) => nativeFrame.ref.data[index] = value, + ); + } else if (frame is CanRemoteFrame) { + nativeFrame.ref.can_id = frame.id; + if (frame is CanExtendedRemoteFrame) { + nativeFrame.ref.can_id |= CAN_EFF_FLAG; + } + nativeFrame.ref.can_id |= CAN_RTR_FLAG; + + nativeFrame.ref.len = 0; + } else if (frame is CanErrorFrame) { + nativeFrame.ref.can_id |= CAN_ERR_FLAG; + + nativeFrame.ref.len = 0; + } + + final ok = retry(() => libc.write(fd, nativeFrame.cast(), ffi.sizeOf())); + + ffi.calloc.free(nativeFrame); + + if (ok < 0) { + throw LinuxError('Couldn\'t write CAN frame to socket.', 'write', libc.errno); + } else if (ok != CAN_MTU) { + throw LinuxError('Incomplete write.', 'write'); + } + } + + static CanFrame canFrameFromNative(can_frame native) { + final eff = native.can_id & CAN_EFF_FLAG != 0; + final rtr = native.can_id & CAN_RTR_FLAG != 0; + final err = native.can_id & CAN_ERR_FLAG != 0; + + if (err) { + return CanFrame.error(); + } else if (eff) { + final id = native.can_id & CAN_EFF_MASK; + + if (rtr) { + return CanFrame.extendedRemote(id: id); + } else { + return CanFrame.extended( + id: id, + data: List.generate( + native.len, + (index) => native.data[index], + growable: false, + ), + ); + } + } else { + final id = native.can_id & CAN_SFF_MASK; + + if (rtr) { + return CanFrame.standardRemote(id: id); + } else { + return CanFrame.standard( + id: id, + data: List.generate( + native.len, + (index) => native.data[index], + growable: false, + ), + ); + } + } + } + + static CanFrame? readStatic(LibC libc, int fd, ffi.Pointer buffer, int bufferSize) { + assert(bufferSize >= ffi.sizeOf()); + assert(CAN_MTU == ffi.sizeOf()); + + final ok = libc.read(fd, buffer, ffi.sizeOf()); + if (ok < 0 && libc.errno == EAGAIN) { + // no frame available right now. + return null; + } else if (ok < 0) { + throw LinuxError('Could not read CAN frame.', 'read', libc.errno); + } + + if (ok != CAN_MTU) { + throw LinuxError('Malformed CAN frame. Expected received frame to be $CAN_MTU bytes large, was: $ok.', 'read'); + } + + return canFrameFromNative(buffer.cast().ref); + } + + CanFrame? read(int fd) { + assert(openFds.contains(fd)); + return readStatic(libc, fd, _buffer, _bufferSize); + } + + /// Opens an rtnetlink socket for kernel network interface manipulation. + /// + /// See: https://man7.org/linux/man-pages/man7/rtnetlink.7.html + int openRtNetlinkSocket() { + final fd = libc.socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (fd < 0) { + throw LinuxError('Couldn\'t open netlink socket.', 'socket', libc.errno); + } + + openFds.add(fd); + + try { + final sndbuf = ffi.malloc(); + final rcvbuf = ffi.malloc(); + + try { + sndbuf.value = 32768; + rcvbuf.value = 32768; + + var ok = libc.setsockopt(fd, SOL_SOCKET, SO_SNDBUF, sndbuf.cast(), ffi.sizeOf()); + if (ok < 0) { + throw LinuxError('Couldn\'t set netlink socket sndbuf size.', 'setsockopt', libc.errno); + } + + libc.setsockopt(fd, SOL_SOCKET, SO_RCVBUF, rcvbuf.cast(), ffi.sizeOf()); + if (ok < 0) { + throw LinuxError('Couldn\'t set netlink socket rcvbuf size.', 'setsockopt', libc.errno); + } + } finally { + ffi.malloc.free(sndbuf); + ffi.malloc.free(rcvbuf); + } + + final local = ffi.calloc(); + + try { + local.ref.nl_family = AF_NETLINK; + local.ref.nl_groups = 0; + + var ok = libc.bind(fd, local.cast(), ffi.sizeOf()); + if (ok < 0) { + throw LinuxError('Could\'t bind netlink socket.', 'bind', libc.errno); + } + + final addrLen = ffi.calloc(); + + try { + addrLen.value = ffi.sizeOf(); + + ok = libc.getsockname(fd, local.cast(), addrLen); + if (ok < 0) { + throw LinuxError('Error', 'getsockname', libc.errno); + } + + if (addrLen.value != ffi.sizeOf()) { + throw StateError('Invalid address length: ${addrLen.value}, should be: ${ffi.sizeOf()}'); + } + + if (local.ref.nl_family != AF_NETLINK) { + throw StateError('Invalid address family ${local.ref.nl_family}, should be: $AF_NETLINK'); + } + } finally { + ffi.calloc.free(addrLen); + } + } finally { + ffi.calloc.free(local); + } + } on Object { + libc.close(fd); + rethrow; + } + + return fd; + } + + void setCanInterfaceAttributes( + int netlinkFd, + int interfaceIndex, { + bool? setUpDown, + CanBitTiming? bitTiming, + Map? ctrlModeFlags, + int? restartMs, + bool restart = false, + CanBitTiming? dataBitTiming, + int? termination, + }) { + final maxLength = NLMSG_SPACE(ffi.sizeOf() + 2048); + + final nl = ffi.calloc.allocate(maxLength); + + try { + nl.ref.nlmsg_len = NLMSG_LENGTH(ffi.sizeOf()); + nl.ref.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + nl.ref.nlmsg_type = RTM_NEWLINK; + + final ifinfo = NLMSG_DATA(nl); + + ifinfo.ref.ifi_family = 0; + ifinfo.ref.ifi_index = interfaceIndex; + ifinfo.ref.ifi_change = 0; + + if (setUpDown == true) { + ifinfo.ref.ifi_change |= IFF_UP; + ifinfo.ref.ifi_flags &= ~IFF_UP; + } else if (setUpDown == false) { + ifinfo.ref.ifi_change |= IFF_UP; + ifinfo.ref.ifi_flags |= IFF_UP; + } + + ffi.Pointer allocateAttr(int type, int lengthInBytes) { + final len = RTA_LENGTH(lengthInBytes); + if (NLMSG_SPACE(NLMSG_ALIGN(nl.ref.nlmsg_len) + RTA_ALIGN(len)) > maxLength) { + throw StateError('rtnetlink message exceeded maximum size of $maxLength'); + } + + final rta = NLMSG_TAIL(nl); + + rta.ref.rta_type = type; + rta.ref.rta_len = len; + + final data = RTA_DATA(rta); + + nl.ref.nlmsg_len = NLMSG_ALIGN(nl.ref.nlmsg_len) + RTA_ALIGN(len); + + return data; + } + + void addUint32Attr(int type, int value) { + final data = allocateAttr(type, ffi.sizeOf()); + data.value = value; + } + + void addStringAttr(int type, String str) { + final encoded = utf8.encode(str); + + final data = allocateAttr(type, encoded.length); + data.asTypedList(encoded.length).setAll(0, encoded); + } + + void addVoidAttr(int type) { + allocateAttr(type, 0); + } + + void addSection(int type, void Function() section) { + final sectionAttr = NLMSG_TAIL(nl); + + addVoidAttr(type); + + section(); + + sectionAttr.ref.rta_len = NLMSG_TAIL(nl).address - sectionAttr.address; + } + + addSection(IFLA_LINKINFO, () { + addStringAttr(IFLA_INFO_KIND, 'can'); + + addSection(IFLA_INFO_DATA, () { + if (restartMs != null) { + addUint32Attr(IFLA_CAN_RESTART_MS, restartMs); + } + + if (restart) { + addUint32Attr(IFLA_CAN_RESTART, 1); + } + + if (bitTiming != null) { + final bittimingPtr = allocateAttr(IFLA_CAN_BITTIMING, ffi.sizeOf()); + + bittimingPtr.ref.bitrate = bitTiming.bitrate; + bittimingPtr.ref.sample_point = bitTiming.samplePoint; + bittimingPtr.ref.tq = bitTiming.tq; + bittimingPtr.ref.prop_seg = bitTiming.propagationSegment; + bittimingPtr.ref.phase_seg1 = bitTiming.phaseSegment1; + bittimingPtr.ref.phase_seg2 = bitTiming.phaseSegment2; + bittimingPtr.ref.sjw = bitTiming.syncJumpWidth; + bittimingPtr.ref.brp = bitTiming.bitratePrescaler; + } + + if (ctrlModeFlags != null) { + final ctrlmodePtr = allocateAttr(IFLA_CAN_CTRLMODE, ffi.sizeOf()); + + for (final flag in ctrlModeFlags.keys) { + ctrlmodePtr.ref.mask |= flag.value; + + if (ctrlModeFlags[flag]!) { + ctrlmodePtr.ref.flags |= flag.value; + } + } + } + + if (dataBitTiming != null) { + final bittimingPtr = allocateAttr(IFLA_CAN_DATA_BITTIMING, ffi.sizeOf()); + + bittimingPtr.ref.bitrate = dataBitTiming.bitrate; + bittimingPtr.ref.sample_point = dataBitTiming.samplePoint; + bittimingPtr.ref.tq = dataBitTiming.tq; + bittimingPtr.ref.prop_seg = dataBitTiming.propagationSegment; + bittimingPtr.ref.phase_seg1 = dataBitTiming.phaseSegment1; + bittimingPtr.ref.phase_seg2 = dataBitTiming.phaseSegment2; + bittimingPtr.ref.sjw = dataBitTiming.syncJumpWidth; + bittimingPtr.ref.brp = dataBitTiming.bitratePrescaler; + } + + if (termination != null) { + final terminationPtr = allocateAttr(IFLA_CAN_TERMINATION, ffi.sizeOf()); + + terminationPtr.value = termination; + } + }); + }); + + final nladdr = ffi.calloc(); + + nladdr.ref.nl_family = AF_NETLINK; + nladdr.ref.nl_pid = 0; + nladdr.ref.nl_groups = 0; + + final iov = ffi.calloc(); + + iov.ref.iov_base = nl.cast(); + iov.ref.iov_len = nl.ref.nlmsg_len; + + final msg = ffi.calloc(); + + msg.ref.msg_name = nladdr.cast(); + msg.ref.msg_namelen = ffi.sizeOf(); + msg.ref.msg_iov = iov; + msg.ref.msg_iovlen = 1; + + const bufLen = 16384; + final buf = ffi.calloc(bufLen); + + // Could be unneccessary + nl.ref.nlmsg_seq = 0; + nl.ref.nlmsg_flags |= NLM_F_ACK; + + try { + var ok = libc.sendmsg(netlinkFd, msg, 0); + if (ok < 0) { + throw LinuxError('Error sending message to rtnetlink.', 'sendmsg', libc.errno); + } + + iov.ref.iov_base = buf.cast(); + while (true) { + iov.ref.iov_len = bufLen; + + ok = libc.recvmsg(netlinkFd, msg, 0); + if (ok < 0) { + throw LinuxError('Could not receive message from rtnetlink.', 'recvmsg', libc.errno); + } + + var bytesRemaining = ok; + + var nh = buf.cast(); + while (NLMSG_OK(nh, bytesRemaining)) { + if (nh.ref.nlmsg_type == NLMSG_DONE) { + // done processing + return; + } + + if (nh.ref.nlmsg_type == NLMSG_ERROR) { + // error + final err = NLMSG_DATA(nh); + if (nh.ref.nlmsg_len < NLMSG_SPACE(ffi.sizeOf())) { + throw LinuxError('Error message received from rtnetlink was truncated.'); + } + + if (err.ref.error < 0) { + throw LinuxError('RTNETLINK answers', null, -err.ref.error); + } else { + return; + } + } + + final tuple = NLMSG_NEXT(nh, bytesRemaining); + nh = tuple.item1; + bytesRemaining = tuple.item2; + } + + if (bytesRemaining > 0) { + throw LinuxError('Message received from rtnetlink was truncated.'); + } + } + } finally { + ffi.calloc.free(nladdr); + ffi.calloc.free(iov); + ffi.calloc.free(msg); + ffi.calloc.free(buf); + } + } finally { + ffi.calloc.free(nl); + } + } + + void sendDumpRequest(int netlinkFd, int interfaceIndex, int family, int type) { + assert(openFds.contains(netlinkFd)); + + final nl = ffi.calloc.allocate(NLMSG_SPACE(ffi.sizeOf())); + + nl.ref.nlmsg_len = NLMSG_LENGTH(ffi.sizeOf()); + nl.ref.nlmsg_type = type; + nl.ref.nlmsg_flags = NLM_F_REQUEST; + nl.ref.nlmsg_pid = 0; + nl.ref.nlmsg_seq = 0; + + final ifi = NLMSG_DATA(nl); + + ifi.ref.ifi_family = family; + ifi.ref.ifi_index = interfaceIndex; + + try { + final ok = libc.send(netlinkFd, nl.cast(), nl.ref.nlmsg_len, 0); + + if (ok < 0) { + throw LinuxError('Could not send info request to RTNETLINK.', 'send', libc.errno); + } + } finally { + ffi.calloc.free(nl); + } + } +} diff --git a/packages/linux_can/pubspec.yaml b/packages/linux_can/pubspec.yaml index 3e28657..7e0b396 100644 --- a/packages/linux_can/pubspec.yaml +++ b/packages/linux_can/pubspec.yaml @@ -13,3 +13,4 @@ dev_dependencies: dependencies: _ardera_common_libc_bindings: ^0.2.0 ffi: ^2.0.1 + quiver: ^3.2.1