diff --git a/src/SocketCANSharp/CanFrameFast.cs b/src/SocketCANSharp/CanFrameFast.cs new file mode 100644 index 0000000..d841538 --- /dev/null +++ b/src/SocketCANSharp/CanFrameFast.cs @@ -0,0 +1,108 @@ +#region License +/* +BSD 3-Clause License + +Copyright (c) 2025, Brill Power +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#endregion + +#if NET8_0_OR_GREATER +using System; +using System.Runtime.InteropServices; + +namespace SocketCANSharp +{ + /// + /// Represents a CAN frame (classical or FD) that exists only on the stack. + /// + public readonly ref struct CanFrameFast + { + private readonly Span _buffer; + private readonly Span _bufferAsUint; + + /// + /// Construct a new CanFrameFast instance with the supplied buffer. + /// + public CanFrameFast(ref Span buffer) + { + if (buffer.Length != 16 && buffer.Length != 72) + { + throw new ArgumentException("Supplied buffer is too small."); + } + _buffer = buffer; + _bufferAsUint = MemoryMarshal.Cast(buffer); + } + + /// + /// Gets a reference to the 11 or 29-bit CAN ID. + /// + public ref uint CanId + { + get { return ref _bufferAsUint[0]; } + } + + /// + /// Frame length in bytes. + /// + public ref byte Length + { + get { return ref _buffer[4]; } + } + + /// + /// CAN FD specific flags for ESI, BRS, etc. + /// + public CanFdFlags Flags + { + get { return (CanFdFlags)_buffer[5]; } + set { _buffer[5] = (byte)value; } + } + + /// + /// CAN frame payload. + /// + public Span Data => _buffer.Slice(8); + + /// + /// Gets the total size of this CAN frame (either 16 or 72 bytes). + /// + public int Size => _buffer.Length; + + /// + /// Indexes into the payload of this CAN frame. + /// + public ref byte this[int index] + { + get { return ref _buffer[8 + index]; } + } + + internal Span Buffer => _buffer; + } +} +#endif // NET8_0_OR_GREATER \ No newline at end of file diff --git a/src/SocketCANSharp/LibcNativeMethods.cs b/src/SocketCANSharp/LibcNativeMethods.cs index 6350b98..7798918 100644 --- a/src/SocketCANSharp/LibcNativeMethods.cs +++ b/src/SocketCANSharp/LibcNativeMethods.cs @@ -1,5 +1,5 @@ #region License -/* +/* BSD 3-Clause License Copyright (c) 2021, Derek Will @@ -28,7 +28,7 @@ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #endregion @@ -89,7 +89,7 @@ public static class LibcNativeMethods public static extern int Ioctl(SafeFileDescriptorHandle socketHandle, int request, [In][Out] IfreqMtu ifreq); /// - /// Used to obtain a timeval struct with the receive timestamp of the last packet passed to the user. + /// Used to obtain a timeval struct with the receive timestamp of the last packet passed to the user. /// /// Socket Handle Wrapper Instance /// Request Code @@ -147,7 +147,19 @@ public static class LibcNativeMethods /// 0 on success, -1 on error [DllImport("libc", EntryPoint="connect", SetLastError=true)] public static extern int Connect(SafeFileDescriptorHandle socketHandle, SockAddrCanJ1939 addr, int addrSize); - + +#if NET8_0_OR_GREATER + /// + /// Write the contents of a byte buffer to the socket. + /// + /// Socket Handle Wrappper Instance + /// Reference to a buffer to write + /// Size of the buffer in bytes + /// The number of bytes written on success, -1 on error + [DllImport("libc", EntryPoint = "write", SetLastError = true)] + internal static extern int Write(SafeFileDescriptorHandle socketHandle, ref byte frame, int frameSize); +#endif // NET8_0_OR_GREATER + /// /// Write the CanFrame to the socket. /// @@ -288,6 +300,18 @@ public static class LibcNativeMethods [DllImport("libc", EntryPoint="write", SetLastError=true)] public static extern int Write(SafeFileDescriptorHandle socketHandle, byte[] data, int dataSize); +#if NET8_0_OR_GREATER + /// + /// Read into a byte buffer from the socket. + /// + /// Socket Handle Wrapper Instance + /// A reference to a byte buffer to populate + /// Size of byte buffer + /// The number of bytes read on success, -1 on error + [DllImport("libc", EntryPoint = "read", SetLastError = true)] + internal static extern int Read(SafeFileDescriptorHandle socketHandle, ref byte frame, int frameSize); +#endif // NET8_0_OR_GREATER + /// /// Read a CanFrame from the socket. /// @@ -337,7 +361,7 @@ public static class LibcNativeMethods /// The number of bytes read on success, -1 on error [DllImport("libc", EntryPoint="read", SetLastError=true)] public static extern int Read(SafeFileDescriptorHandle socketHandle, [Out] BcmGenericMessage message, int msgSize); - + /// /// Read a BcmGenericMessage from the socket. Variant for 32-bit. /// @@ -715,7 +739,7 @@ public static class LibcNativeMethods /// 0 or 1 on success depending on option name and value, -1 on error [DllImport("libc", EntryPoint="setsockopt", SetLastError=true)] public static extern int SetSockOpt(SafeFileDescriptorHandle socketHandle, SocketLevel socketLevel, J1939SocketOptions optionName, ref int optionValue, int optionValueSize); - + /// /// Get the socket option specified by the option name and socket level to the provided option value for the supplied socket. /// @@ -751,7 +775,7 @@ public static class LibcNativeMethods /// 0 on success, -1 on error [DllImport("libc", EntryPoint="getsockopt", SetLastError=true)] public static extern int GetSockOpt(SafeFileDescriptorHandle socketHandle, SocketLevel socketLevel, J1939SocketOptions optionName, [In, Out] J1939Filter[] filters, ref int optionValueSize); - + /// /// Set the socket option specified by the option name and socket level to the provided option value for the supplied socket. /// @@ -775,7 +799,7 @@ public static class LibcNativeMethods /// 0 on success, -1 on error [DllImport("libc", EntryPoint = "getsockopt", SetLastError = true)] public static extern int GetSockOpt(SafeFileDescriptorHandle socketHandle, SocketLevel socketLevel, int optionName, IntPtr optionValue, ref int optionValueSize); - + /// /// Set the socket option specified by the option name and socket level to the provided option value for the supplied socket. /// @@ -817,12 +841,12 @@ public static class LibcNativeMethods /// /// Opens an epoll file descriptor. /// - /// The size argument is ignored since Linux 2.6.8, but must be greater than zero for backwards compatibility. + /// The size argument is ignored since Linux 2.6.8, but must be greater than zero for backwards compatibility. /// Originally, this argument was intended as a hint to the kernel as to the number of file descriptors that the caller expected to add to the epoll instance. /// On success, returns a valid file descriptor handle. On failure, returns an invalid file descriptor handle. [DllImport("libc", EntryPoint="epoll_create", SetLastError=true)] public static extern SafeFileDescriptorHandle EpollCreate(int size); - + /// /// Control interface used to add, modify, and delete entries from the interest list of an epoll file descriptor. /// @@ -833,7 +857,7 @@ public static class LibcNativeMethods /// 0 on success, -1 on error [DllImport("libc", EntryPoint="epoll_ctl", SetLastError=true)] public static extern int EpollControl(SafeFileDescriptorHandle epfd, EpollOperation op, SafeFileDescriptorHandle fd, ref EpollEvent evnt); - + /// /// Waits for an I/O event on an epoll file descriptor. /// @@ -902,7 +926,7 @@ public static class LibcNativeMethods /// The number of bytes received on success, -1 on error [DllImport("libc", EntryPoint="recvmsg", SetLastError=true)] public static extern int RecvMsg(SafeFileDescriptorHandle socketHandle, ref MessageHeader canMessage, MessageFlags flags); - + /// /// Retrieves the index of the network interface corresponding to the specified name. /// diff --git a/src/SocketCANSharp/Network/RawCanSocket.cs b/src/SocketCANSharp/Network/RawCanSocket.cs index 2dc3152..d421d2d 100644 --- a/src/SocketCANSharp/Network/RawCanSocket.cs +++ b/src/SocketCANSharp/Network/RawCanSocket.cs @@ -1,5 +1,5 @@ #region License -/* +/* BSD 3-Clause License Copyright (c) 2022, Derek Will @@ -28,7 +28,7 @@ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #endregion @@ -57,7 +57,7 @@ public SockAddrCan Address /// /// CAN Filters to control the reception of CAN frames. /// - public CanFilter[] CanFilters + public CanFilter[] CanFilters { get { @@ -72,7 +72,7 @@ public CanFilter[] CanFilters /// /// Error Mask to filter which Error Message Frames are passed to the socket receive queue. /// - public CanErrorClass ErrorFilters + public CanErrorClass ErrorFilters { get { @@ -87,7 +87,7 @@ public CanErrorClass ErrorFilters /// /// Local loopback to receive messages sent from other sockets on this CAN node. /// - public bool LocalLoopback + public bool LocalLoopback { get { @@ -102,7 +102,7 @@ public bool LocalLoopback /// /// Enables a socket to receive the messages that it sent itself. /// - public bool ReceiveOwnMessages + public bool ReceiveOwnMessages { get { @@ -117,7 +117,7 @@ public bool ReceiveOwnMessages /// /// Allows the socket to handle CAN FD frames. /// - public bool EnableCanFdFrames + public bool EnableCanFdFrames { get { @@ -132,7 +132,7 @@ public bool EnableCanFdFrames /// /// Allows the socket to handle CAN XL frames. /// - public bool EnableCanXlFrames + public bool EnableCanXlFrames { get { @@ -146,9 +146,9 @@ public bool EnableCanXlFrames /// - /// If true, then all CAN filters must match (logical AND) for a CAN frame to be placed into the receive queue. If false, then if any CAN filter matches (logical OR) then a CAN frame will be placed into the receive queue. + /// If true, then all CAN filters must match (logical AND) for a CAN frame to be placed into the receive queue. If false, then if any CAN filter matches (logical OR) then a CAN frame will be placed into the receive queue. /// - public bool AllCanFiltersMustMatch + public bool AllCanFiltersMustMatch { get { @@ -173,7 +173,7 @@ public RawCanSocket() if (SafeHandle.IsInvalid) throw new SocketCanException("Failed to create CAN_RAW socket."); } - + /// /// Assigns the SocketCAN Base Address Structure to the CAN_RAW socket. /// @@ -203,7 +203,7 @@ public void Bind(SockAddrCan addr) /// CanNetworkInterface instance is null. public void Bind(CanNetworkInterface iface) { - if (iface == null) + if (iface == null) throw new ArgumentNullException(nameof(iface)); Bind(new SockAddrCan() @@ -213,6 +213,27 @@ public void Bind(CanNetworkInterface iface) }); } +#if NET8_0_OR_GREATER + /// + /// Writes the supplied CAN Frame to the socket. + /// + /// CAN Frame to transmit onto the CAN network. + /// Number of bytes written to the socket. + /// The socket has been closed. + /// Writing to the underlying CAN_RAW socket failed. + public int Write(ref CanFrameFast canFrame) + { + if (_disposed) + throw new ObjectDisposedException(GetType().FullName); + + int bytesWritten = LibcNativeMethods.Write(SafeHandle, ref MemoryMarshal.GetReference(canFrame.Buffer), canFrame.Size); + if (bytesWritten == -1) + throw new SocketCanException("Writing to the underlying CAN_RAW socket failed."); + + return bytesWritten; + } +#endif // NET8_0_OR_GREATER + /// /// Writes the supplied Classical CAN Frame to the socket. /// @@ -270,6 +291,27 @@ public int Write(CanXlFrame canXlFrame) return bytesWritten; } +#if NET8_0_OR_GREATER + /// + /// Reads a Classical CAN Frame from the socket. + /// + /// Classical CAN Frame to receive from the CAN network. + /// Number of bytes read from the socket. + /// The socket has been closed. + /// Reading from the underlying CAN_RAW socket failed. + public int Read(ref CanFrameFast canFrame) + { + if (_disposed) + throw new ObjectDisposedException(GetType().FullName); + + int bytesRead = LibcNativeMethods.Read(SafeHandle, ref MemoryMarshal.GetReference(canFrame.Buffer), canFrame.Size); + if (bytesRead == -1) + throw new SocketCanException("Reading from the underlying CAN_RAW socket failed."); + + return bytesRead; + } +#endif // NET8_0_OR_GREATER + /// /// Reads a Classical CAN Frame from the socket. /// @@ -389,7 +431,7 @@ private void SetRawCanFilters(CanFilter[] canFilterArray) int len = canFilterArray != null ? Marshal.SizeOf(typeof(CanFilter)) * canFilterArray.Length : 0; int result = LibcNativeMethods.SetSockOpt(SafeHandle, SocketLevel.SOL_CAN_RAW, CanSocketOptions.CAN_RAW_FILTER, canFilterArray, len); if (result != 0) - throw new SocketCanException("Unable to set CAN_RAW_FILTER on CAN_RAW socket."); + throw new SocketCanException("Unable to set CAN_RAW_FILTER on CAN_RAW socket."); } private CanFilter[] GetRawCanFilters() @@ -430,14 +472,14 @@ private void SetRawCanErrorFrameFilters(CanErrorClass errorMask) uint err_mask = (uint)errorMask; int result = LibcNativeMethods.SetSockOpt(SafeHandle, SocketLevel.SOL_CAN_RAW, CanSocketOptions.CAN_RAW_ERR_FILTER, ref err_mask, Marshal.SizeOf(err_mask)); if (result != 0) - throw new SocketCanException("Unable to set CAN_RAW_ERR_FILTER on CAN_RAW socket."); + throw new SocketCanException("Unable to set CAN_RAW_ERR_FILTER on CAN_RAW socket."); } private CanErrorClass GetRawCanErrorFrameFilters() { if (_disposed) throw new ObjectDisposedException(GetType().FullName); - + uint err_mask = 0; int len = Marshal.SizeOf(err_mask); int result = LibcNativeMethods.GetSockOpt(SafeHandle, SocketLevel.SOL_CAN_RAW, CanSocketOptions.CAN_RAW_ERR_FILTER, ref err_mask, ref len); @@ -455,7 +497,7 @@ private void SetLoopback(bool enable) int loopback = enable ? 1 : 0; int result = LibcNativeMethods.SetSockOpt(SafeHandle, SocketLevel.SOL_CAN_RAW, CanSocketOptions.CAN_RAW_LOOPBACK, ref loopback, Marshal.SizeOf(loopback)); - + if (result != 0) throw new SocketCanException("Unable to set CAN_RAW_LOOPBACK on CAN_RAW socket."); } @@ -482,7 +524,7 @@ private void SetReceiveOwnMessages(bool enable) int recv_own_msgs = enable ? 1 : 0; int result = LibcNativeMethods.SetSockOpt(SafeHandle, SocketLevel.SOL_CAN_RAW, CanSocketOptions.CAN_RAW_RECV_OWN_MSGS, ref recv_own_msgs, Marshal.SizeOf(recv_own_msgs)); - + if (result != 0) throw new SocketCanException("Unable to set CAN_RAW_RECV_OWN_MSGS on CAN_RAW socket."); } @@ -509,7 +551,7 @@ private void SetEnableCanFdFrames(bool enable) int can_fd_enabled = enable ? 1 : 0; int result = LibcNativeMethods.SetSockOpt(SafeHandle, SocketLevel.SOL_CAN_RAW, CanSocketOptions.CAN_RAW_FD_FRAMES, ref can_fd_enabled, Marshal.SizeOf(can_fd_enabled)); - + if (result != 0) throw new SocketCanException("Unable to set CAN_RAW_FD_FRAMES on CAN_RAW socket."); } @@ -536,7 +578,7 @@ private void SetEnableCanXlFrames(bool enable) int can_xl_enabled = enable ? 1 : 0; int result = LibcNativeMethods.SetSockOpt(SafeHandle, SocketLevel.SOL_CAN_RAW, CanSocketOptions.CAN_RAW_XL_FRAMES, ref can_xl_enabled, Marshal.SizeOf()); - + if (result != 0) throw new SocketCanException("Unable to set CAN_RAW_XL_FRAMES on CAN_RAW socket."); } @@ -563,7 +605,7 @@ private void SetAllCanFiltersMustMatch(bool enable) int join_filter = enable ? 1 : 0; int result = LibcNativeMethods.SetSockOpt(SafeHandle, SocketLevel.SOL_CAN_RAW, CanSocketOptions.CAN_RAW_JOIN_FILTERS, ref join_filter, Marshal.SizeOf(join_filter)); - + if (result != 0) throw new SocketCanException("Unable to set CAN_RAW_JOIN_FILTERS on CAN_RAW socket."); } @@ -600,12 +642,12 @@ private SockAddrCan GetSockAddr() private int Read(out T frame, out bool txSuccess, out bool localhost) { - int ctrlMsgSize = ControlMessageMacros.CMSG_SPACE(Marshal.SizeOf()) + ControlMessageMacros.CMSG_SPACE(Marshal.SizeOf()); + int ctrlMsgSize = ControlMessageMacros.CMSG_SPACE(Marshal.SizeOf()) + ControlMessageMacros.CMSG_SPACE(Marshal.SizeOf()); IntPtr addrPtr = Marshal.AllocHGlobal(Marshal.SizeOf()); IntPtr iovecPtr = Marshal.AllocHGlobal(Marshal.SizeOf()); IntPtr framePtr = Marshal.AllocHGlobal(Marshal.SizeOf()); IntPtr ctrlMsgPtr = Marshal.AllocHGlobal(ctrlMsgSize); - + try { var iovec = new IoVector() { Base = framePtr, Length = new IntPtr(Marshal.SizeOf()) }; diff --git a/src/SocketCANSharp/SocketCANSharp.csproj b/src/SocketCANSharp/SocketCANSharp.csproj index d151832..b601a5b 100644 --- a/src/SocketCANSharp/SocketCANSharp.csproj +++ b/src/SocketCANSharp/SocketCANSharp.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + netstandard2.0;net8.0 Library SocketCANSharp 0.13.0.0 diff --git a/test/SocketCANSharpTest/RawCanSocketTests.cs b/test/SocketCANSharpTest/RawCanSocketTests.cs index 402630a..3523841 100644 --- a/test/SocketCANSharpTest/RawCanSocketTests.cs +++ b/test/SocketCANSharpTest/RawCanSocketTests.cs @@ -1,5 +1,5 @@ #region License -/* +/* BSD 3-Clause License Copyright (c) 2022, Derek Will @@ -28,7 +28,7 @@ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #endregion @@ -39,6 +39,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE using System.Net.Sockets; using System.Collections.Generic; using System.Linq; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; namespace SocketCANSharpTest { @@ -269,7 +270,7 @@ public void RawCanSocket_ErrorFilters_Success_Test() { rawCanSocket.ErrorFilters = CanErrorClass.CAN_ERR_ACK | CanErrorClass.CAN_ERR_BUSERROR; CanErrorClass errFilters = rawCanSocket.ErrorFilters; - Assert.IsTrue(errFilters.HasFlag(CanErrorClass.CAN_ERR_ACK)); + Assert.IsTrue(errFilters.HasFlag(CanErrorClass.CAN_ERR_ACK)); Assert.IsTrue(errFilters.HasFlag(CanErrorClass.CAN_ERR_BUSERROR)); Assert.IsFalse(errFilters.HasFlag(CanErrorClass.CAN_ERR_BUSOFF)); Assert.IsFalse(errFilters.HasFlag(CanErrorClass.CAN_ERR_CRTL)); @@ -319,7 +320,7 @@ public void RawCanSocket_ErrorFilters_Post_Bind_Assignment_Success_Test() rawCanSocket.Bind(iface); rawCanSocket.ErrorFilters = CanErrorClass.CAN_ERR_ACK | CanErrorClass.CAN_ERR_BUSERROR; CanErrorClass errFilters = rawCanSocket.ErrorFilters; - Assert.IsTrue(errFilters.HasFlag(CanErrorClass.CAN_ERR_ACK)); + Assert.IsTrue(errFilters.HasFlag(CanErrorClass.CAN_ERR_ACK)); Assert.IsTrue(errFilters.HasFlag(CanErrorClass.CAN_ERR_BUSERROR)); Assert.IsFalse(errFilters.HasFlag(CanErrorClass.CAN_ERR_BUSOFF)); Assert.IsFalse(errFilters.HasFlag(CanErrorClass.CAN_ERR_CRTL)); @@ -1145,5 +1146,131 @@ public void RawCanSocket_Read_CanXlFrame_TxSuccess_And_Localhost_Success_Test() Assert.AreEqual(true, localhost2); } } + + [Test] + public void RawCanSocket_Read_CanFrameFast_Success_Test() + { + IEnumerable collection = CanNetworkInterface.GetAllInterfaces(true); + Assert.IsNotNull(collection); + Assert.GreaterOrEqual(collection.Count(), 1); + + var iface = collection.FirstOrDefault(i => i.Name.Equals("vcan0")); + Assert.IsNotNull(iface); + + using (var senderSocket = new RawCanSocket()) + using (var receiverSocket = new RawCanSocket()) + { + senderSocket.Bind(iface); + receiverSocket.Bind(iface); + + byte[] data = [ 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef ]; + Span write = stackalloc byte[16]; + CanFrameFast writeFrame = new CanFrameFast(ref write); + writeFrame.CanId = 0x123; + writeFrame.Length = (byte)data.Length; + data.CopyTo(writeFrame.Data); + int bytesWritten = senderSocket.Write(ref writeFrame); + Assert.AreEqual(16, bytesWritten); + + Span read = stackalloc byte[16]; + CanFrameFast readFrame = new CanFrameFast(ref read); + int bytesRead = receiverSocket.Read(ref readFrame); + Assert.AreEqual(16, bytesRead); + Assert.AreEqual(0x123, readFrame.CanId); + Assert.IsTrue(readFrame.Data.ToArray().Take(readFrame.Length).SequenceEqual(new byte[] { 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef })); + } + } + + [Test] + public void RawCanSocket_Write_CanFrameFast_Success_Test() + { + IEnumerable collection = CanNetworkInterface.GetAllInterfaces(true); + Assert.IsNotNull(collection); + Assert.GreaterOrEqual(collection.Count(), 1); + + var iface = collection.FirstOrDefault(i => i.Name.Equals("vcan0")); + Assert.IsNotNull(iface); + + using (var rawCanSocket = new RawCanSocket()) + { + rawCanSocket.Bind(iface); + + byte[] data = [ 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef ]; + Span write = stackalloc byte[16]; + CanFrameFast writeFrame = new CanFrameFast(ref write); + writeFrame.CanId = 0x123; + writeFrame.Length = (byte)data.Length; + data.CopyTo(writeFrame.Data); + int bytesWritten = rawCanSocket.Write(ref writeFrame); + Assert.AreEqual(16, bytesWritten); + } + } + + [Test] + public void RawCanSocket_Write_CanFrameFast_Fd_Success_Test() + { + IEnumerable collection = CanNetworkInterface.GetAllInterfaces(true); + Assert.IsNotNull(collection); + Assert.GreaterOrEqual(collection.Count(), 1); + + var iface = collection.FirstOrDefault(i => i.Name.Equals("vcan0")); + Assert.IsNotNull(iface); + + using (var rawCanSocket = new RawCanSocket()) + { + Assume.That(iface.MaximumTransmissionUnit, Is.GreaterThanOrEqualTo(SocketCanConstants.CANFD_MTU)); + rawCanSocket.EnableCanFdFrames = true; + rawCanSocket.Bind(iface); + + byte[] data = [ 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef ]; + Span write = stackalloc byte[72]; + CanFrameFast writeFrame = new CanFrameFast(ref write); + writeFrame.CanId = 0x123; + writeFrame.Length = (byte)data.Length; + writeFrame.Flags = CanFdFlags.CANFD_BRS; + data.CopyTo(writeFrame.Data); + int bytesWritten = rawCanSocket.Write(ref writeFrame); + Assert.AreEqual(72, bytesWritten); + } + } + + [Test] + public void RawCanSocket_Read_CanFrameFast_Fd_Success_Test() + { + IEnumerable collection = CanNetworkInterface.GetAllInterfaces(true); + Assert.IsNotNull(collection); + Assert.GreaterOrEqual(collection.Count(), 1); + + var iface = collection.FirstOrDefault(i => i.Name.Equals("vcan0")); + Assert.IsNotNull(iface); + + using (var senderSocket = new RawCanSocket()) + using (var receiverSocket = new RawCanSocket()) + { + Assume.That(iface.MaximumTransmissionUnit, Is.GreaterThanOrEqualTo(SocketCanConstants.CANFD_MTU)); + senderSocket.EnableCanFdFrames = true; + receiverSocket.EnableCanFdFrames = true; + senderSocket.Bind(iface); + receiverSocket.Bind(iface); + + byte[] data = [ 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef ]; + Span write = stackalloc byte[72]; + CanFrameFast writeFrame = new CanFrameFast(ref write); + writeFrame.CanId = 0x123; + writeFrame.Length = (byte)data.Length; + writeFrame.Flags = CanFdFlags.CANFD_BRS; + data.CopyTo(writeFrame.Data); + int bytesWritten = senderSocket.Write(ref writeFrame); + Assert.AreEqual(72, bytesWritten); + + Span read = stackalloc byte[72]; + CanFrameFast readFrame = new CanFrameFast(ref read); + int bytesRead = receiverSocket.Read(ref readFrame); + Assert.AreEqual(72, bytesRead); + Assert.AreEqual(0x123, readFrame.CanId); + Assert.IsTrue(readFrame.Data.ToArray().Take(readFrame.Length).SequenceEqual(new byte[] { 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef })); + Assert.IsTrue(readFrame.Flags.HasFlag(CanFdFlags.CANFD_BRS)); // In Kernel 6.1 and higher - CANFD_FDF flag will also be set. Changing to just check for BRS to be backwards compatible. + } + } } }