Skip to content

Commit

Permalink
first attempt add recvmsg/sendto support for J1939
Browse files Browse the repository at this point in the history
if this works out it will be extended to RAW sockets
  • Loading branch information
pschichtel committed Feb 18, 2024
1 parent 9ac3b70 commit 58d01a4
Show file tree
Hide file tree
Showing 16 changed files with 573 additions and 149 deletions.
3 changes: 0 additions & 3 deletions cmake/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ project(javacan-jni C)
set(CMAKE_C_STANDARD 99)

include_directories(../core/src/include)
include_directories(../core/src/include/linux)
include_directories(../core/src/include/linux/can)
include_directories(../core/src/main/c)
include_directories(../core/target/java-jni-headers)
include_directories(../core/target/java-jni-headers/linux)
include_directories(../core/target/jni)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@

import org.junit.jupiter.api.Test;
import tel.schich.javacan.CanChannels;
import tel.schich.javacan.J1939Address;
import tel.schich.javacan.J1939CanChannel;

import java.nio.ByteBuffer;

import static org.junit.jupiter.api.Assertions.*;
import static tel.schich.javacan.CanSocketOptions.*;
import static tel.schich.javacan.TestHelper.assertByteBufferEquals;
import static tel.schich.javacan.TestHelper.directBufferOf;
import static tel.schich.javacan.test.CanTestHelper.CAN_INTERFACE;
Expand All @@ -39,19 +39,22 @@ class J1939CanSocketTest {
@Test
void testLoopback() throws Exception {

J1939Address source = new J1939Address(CAN_INTERFACE, J1939Address.NO_NAME, J1939Address.NO_PGN, (byte) 0x20);
J1939Address destination = new J1939Address(CAN_INTERFACE, J1939Address.NO_NAME, J1939Address.NO_PGN, (byte) 0x30);

try (final J1939CanChannel a = CanChannels.newJ1939Channel()) {
a.bind(CAN_INTERFACE, J1939CanChannel.NO_NAME, J1939CanChannel.NO_PGN, (short) 0x20);
a.connect(CAN_INTERFACE, J1939CanChannel.NO_NAME, J1939CanChannel.NO_PGN, (short) 0x30);
a.bind(source);
a.connect(destination);

try (final J1939CanChannel b = CanChannels.newJ1939Channel()) {
b.bind(CAN_INTERFACE, J1939CanChannel.NO_NAME, J1939CanChannel.NO_PGN, (short) 0x30);
b.connect(CAN_INTERFACE, J1939CanChannel.NO_NAME, J1939CanChannel.NO_PGN, (short) 0x20);
b.bind(destination);
b.connect(source);
b.configureBlocking(false);

final ByteBuffer inputBuffer = directBufferOf(new byte[]{0x20, 0x33});
final ByteBuffer outputBuffer = ByteBuffer.allocateDirect(inputBuffer.capacity() + 1);
assertEquals(2, a.write(inputBuffer));
assertEquals(2, b.read(outputBuffer));
assertEquals(2, a.sendData(inputBuffer));
assertEquals(2, b.receiveData(outputBuffer));
inputBuffer.flip();
outputBuffer.flip();
assertByteBufferEquals(inputBuffer, outputBuffer);
Expand Down
4 changes: 4 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/c/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@
#include <errno.h>
#include <fcntl.h>
#include <javacan-core/jni-c-to-java.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <string.h>
#include <poll.h>
#include <sys/socket.h>
#include <bits/types/struct_timeval.h>

inline int create_can_raw_socket() {
return socket(PF_CAN, SOCK_RAW, CAN_RAW);
Expand Down Expand Up @@ -92,7 +92,7 @@ int set_timeout(int sock, int type, uint64_t seconds, uint64_t nanos) {
socklen_t timeout_len = sizeof(struct timeval);
struct timeval timeout;
timeout.tv_sec = (time_t) seconds;
timeout.tv_usec = (suseconds_t) nanos / 1000;
timeout.tv_usec = (__suseconds_t) nanos / 1000;

return setsockopt(sock, SOL_SOCKET, type, &timeout, timeout_len);
}
Expand Down
1 change: 0 additions & 1 deletion core/src/main/c/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
#include <jni.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/time.h>

#define MICROS_PER_SECOND 1000000

Expand Down
218 changes: 168 additions & 50 deletions core/src/main/c/javacan_socketcan.c

Large diffs are not rendered by default.

58 changes: 52 additions & 6 deletions core/src/main/java/tel/schich/javacan/AbstractCanChannel.java
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,7 @@ public <T> T getOption(SocketOption<T> option) throws IOException {
* @throws IOException if the native call fails
*/
protected long readSocket(ByteBuffer buffer) throws IOException {
if (!buffer.isDirect()) {
throw new IllegalArgumentException("The buffer must be a direct buffer!");
}
ensureDirectBuffer(buffer);
try {
int pos = buffer.position();
int bytesRead = (int) SocketCAN.read(sock, buffer, pos, buffer.remaining());
Expand All @@ -218,6 +216,28 @@ protected long readSocket(ByteBuffer buffer) throws IOException {
}
}

/**
* Receives a message from this socket into the given {@link java.nio.ByteBuffer}.
* The {@link java.nio.ByteBuffer} must be a direct buffer as it is passed into native code.
* Buffer position and limit will be respected and the position will be updated.
*
* @see <a href="https://man7.org/linux/man-pages/man2/recv.2.html">recv man page</a>
* @param buffer the buffer to receive into
* @return The number of bytes receive from the socket
* @throws IOException if the native call fails
*/
protected long receiveFromSocket(ByteBuffer buffer, int flags) throws IOException {
ensureDirectBuffer(buffer);
try {
int pos = buffer.position();
int bytesReceived = (int) SocketCAN.receive(sock, buffer, pos, buffer.remaining(), flags);
buffer.position(pos + bytesReceived);
return bytesReceived;
} catch (LinuxNativeOperationException e) {
throw checkForClosedChannel(e);
}
}

/**
* Writes data to this socket from the given {@link java.nio.ByteBuffer}.
* The {@link java.nio.ByteBuffer} must be a direct buffer as it is passed into native code.
Expand All @@ -229,9 +249,7 @@ protected long readSocket(ByteBuffer buffer) throws IOException {
* @throws IOException if the native call fails
*/
protected long writeSocket(ByteBuffer buffer) throws IOException {
if (!buffer.isDirect()) {
throw new IllegalArgumentException("The buffer must be a direct buffer!");
}
ensureDirectBuffer(buffer);
try {
int pos = buffer.position();
int bytesWritten = (int) SocketCAN.write(sock, buffer, pos, buffer.remaining());
Expand All @@ -242,11 +260,39 @@ protected long writeSocket(ByteBuffer buffer) throws IOException {
}
}

/**
* Sends data to this socket from the given {@link java.nio.ByteBuffer}.
* The {@link java.nio.ByteBuffer} must be a direct buffer as it is passed into native code.
* Buffer position and limit will be respected and the position will be updated.
*
* @see <a href="https://man7.org/linux/man-pages/man2/send.2.html">send man page</a>
* @param buffer the buffer to send from
* @return The number of bytes sent to the socket
* @throws IOException if the native call fails
*/
protected long sendToSocket(ByteBuffer buffer, int flags) throws IOException {
ensureDirectBuffer(buffer);
try {
int pos = buffer.position();
int bytesReceived = (int) SocketCAN.send(sock, buffer, pos, buffer.remaining(), flags);
buffer.position(pos + bytesReceived);
return bytesReceived;
} catch (LinuxNativeOperationException e) {
throw checkForClosedChannel(e);
}
}

@Override
public String toString() {
return getClass().getSimpleName() + "(device=" + getDevice() + ", handle=" + getHandle() + ")";
}

protected static void ensureDirectBuffer(ByteBuffer buffer) {
if (!buffer.isDirect()) {
throw new IllegalArgumentException("The buffer must be a direct buffer!");
}
}

protected static IOException checkForClosedChannel(LinuxNativeOperationException orig) throws IOException {
if (orig.isBadFD()) {
final ClosedChannelException ex = new ClosedChannelException();
Expand Down
37 changes: 19 additions & 18 deletions core/src/main/java/tel/schich/javacan/CanChannels.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
*/
package tel.schich.javacan;

import org.checkerframework.checker.nullness.qual.NonNull;

import java.io.IOException;

/**
Expand Down Expand Up @@ -86,32 +88,31 @@ public static J1939CanChannel newJ1939Channel() throws IOException {
}

/**
* Creates a new {@link tel.schich.javacan.J1939CanChannel} already bound to the given
* {@link NetworkDevice}.
* Creates a new {@link tel.schich.javacan.J1939CanChannel} already bound and connected to the given
* {@link J1939Address} pair.
*
* @param device the device to bind to
* @param source the source address to bind to
* @param destination the source address to bind to
* @return The new channel
* @throws IOException if the native socket could not be created or not be bound
* @throws IOException if the native socket could not be created be bound
* @see <a href="https://man7.org/linux/man-pages/man2/socket.2.html">socket man page</a>
*/
public static J1939CanChannel newJ1939Channel(NetworkDevice device) throws IOException {
public static J1939CanChannel newJ1939Channel(@NonNull J1939Address source, @NonNull J1939Address destination) throws IOException {
J1939CanChannel ch = newJ1939Channel();
ch.bind(device);
try {
ch.bind(source);
ch.connect(destination);
} catch (Throwable t) {
try {
ch.close();
} catch (Throwable ct) {
t.addSuppressed(ct);
}
throw t;
}
return ch;
}

/**
* Creates a new {@link tel.schich.javacan.J1939CanChannel} already bound to the given device.
*
* @param device the device to bind to
* @return The new channel
* @throws IOException if the native socket could not be created or not be bound
* @see <a href="https://man7.org/linux/man-pages/man2/socket.2.html">socket man page</a>
*/
public static J1939CanChannel newJ1939Channel(String device) throws IOException {
return newJ1939Channel(NetworkDevice.lookup(device));
}

/**
* Creates a new {@link BcmCanChannel} without binding it to a device.
*
Expand Down
71 changes: 71 additions & 0 deletions core/src/main/java/tel/schich/javacan/J1939Address.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* The MIT License
* Copyright © 2018 Phillip Schichtel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package tel.schich.javacan;

import tel.schich.javacan.platform.linux.LinuxNetworkDevice;

public class J1939Address {
public static final long NO_NAME = 0L;
public static final int NO_PGN = 0x40000;
public static final byte NO_ADDR = (byte) 0xFF;
public static final byte IDLE_ADDR = (byte) 0xFE;

private final LinuxNetworkDevice device;
private final long name;
private final int parameterGroupName;
private final byte address;

public J1939Address(NetworkDevice device, long name, int parameterGroupName, byte address) {
if (!(device instanceof LinuxNetworkDevice)) {
throw new IllegalArgumentException("Unsupported network device given!");
}
this.device = (LinuxNetworkDevice) device;
this.name = name;
this.parameterGroupName = parameterGroupName;
this.address = address;
}

public J1939Address(NetworkDevice device) {
this(device, NO_NAME, NO_PGN, NO_ADDR);
}

public NetworkDevice getDevice() {
return device;
}

LinuxNetworkDevice getLinuxDevice() {
return device;
}

public long getName() {
return name;
}

public int getParameterGroupName() {
return parameterGroupName;
}

public byte getAddress() {
return address;
}
}
Loading

0 comments on commit 58d01a4

Please sign in to comment.