Skip to content

Commit ccdd92c

Browse files
committed
add optional validation of socket options
this allows to catch invalid configurations early and provides more details than provided by the kernel (EINVAL). The setOptionUnsafe method allows to bypass the validation.
1 parent 319809c commit ccdd92c

File tree

8 files changed

+137
-22
lines changed

8 files changed

+137
-22
lines changed

core-test/src/test/java/tel/schich/javacan/test/RawCanSocketTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import tel.schich.javacan.CanChannels;
2727
import tel.schich.javacan.CanFilter;
2828
import tel.schich.javacan.CanFrame;
29+
import tel.schich.javacan.CanSocketOptions;
2930
import tel.schich.javacan.JavaCAN;
3031
import tel.schich.javacan.RawCanChannel;
3132
import tel.schich.javacan.platform.linux.LinuxNativeOperationException;
@@ -122,6 +123,21 @@ void testFilters() throws Exception {
122123
}
123124
}
124125

126+
void testMaxFilterValidation() throws Exception {
127+
try (final RawCanChannel socket = CanChannels.newRawChannel()) {
128+
socket.bind(CAN_INTERFACE);
129+
130+
CanFilter[] filters = new CanFilter[MAX_FILTERS + 1];
131+
for (int i = 0; i < filters.length; i++) {
132+
filters[i] = new CanFilter(0x1, 0x1);
133+
}
134+
135+
assertThrows(IllegalArgumentException.class, () -> socket.setOption(FILTER, filters));
136+
137+
assertThrows(LinuxNativeOperationException.class, () -> socket.setOptionUnsafe(FILTER, filters));
138+
}
139+
}
140+
125141
@Test
126142
void testNonBlockingRead() throws Exception {
127143
try (final RawCanChannel socket = CanChannels.newRawChannel()) {

core-test/src/test/java/tel/schich/javacan/test/isotp/IsotpCanSocketOptionsTest.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,13 @@
2626
import org.slf4j.Logger;
2727
import org.slf4j.LoggerFactory;
2828
import tel.schich.javacan.*;
29+
import tel.schich.javacan.platform.linux.LinuxNativeOperationException;
2930

3031
import java.io.IOException;
3132
import java.nio.ByteBuffer;
3233

33-
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
34-
import static org.junit.jupiter.api.Assertions.assertEquals;
34+
import static org.junit.jupiter.api.Assertions.*;
35+
import static tel.schich.javacan.CanFrame.FD_FLAG_BIT_RATE_SWITCH;
3536
import static tel.schich.javacan.IsotpCanSocketOptions.*;
3637
import static tel.schich.javacan.IsotpSocketAddress.isotpAddress;
3738
import static tel.schich.javacan.test.CanTestHelper.CAN_INTERFACE;
@@ -59,7 +60,7 @@ void testOptions() throws IOException {
5960
LOGGER.debug(String.valueOf(linkLayerOpts));
6061
IsotpLinkLayerOptions customizedLinkLayerOpts = IsotpLinkLayerOptions.DEFAULT
6162
.withMaximumTransmissionUnit(RawCanChannel.FD_MTU)
62-
.withTransmissionFlags(CanFrame.FD_FLAG_BIT_RATE_SWITCH);
63+
.withTransmissionFlags(FD_FLAG_BIT_RATE_SWITCH);
6364
a.setOption(LL_OPTS, customizedLinkLayerOpts);
6465
assertEquals(customizedLinkLayerOpts, a.getOption(LL_OPTS), "What goes in should come out");
6566

@@ -73,6 +74,27 @@ void testOptions() throws IOException {
7374
}
7475
}
7576

77+
@Test
78+
void testLinkLayerOptionsValidations() throws Exception {
79+
try (final IsotpCanChannel a = CanChannels.newIsotpChannel()) {
80+
// unpadded data length
81+
assertThrows(IllegalArgumentException.class, () -> a.setOption(LL_OPTS, IsotpLinkLayerOptions.DEFAULT.withTransmissionDataLength(11)));
82+
assertThrows(LinuxNativeOperationException.class, () -> a.setOptionUnsafe(LL_OPTS, IsotpLinkLayerOptions.DEFAULT.withTransmissionFlags(11)));
83+
84+
// invalid MTU
85+
assertThrows(IllegalArgumentException.class, () -> a.setOption(LL_OPTS, IsotpLinkLayerOptions.DEFAULT.withMaximumTransmissionUnit(30)));
86+
assertThrows(LinuxNativeOperationException.class, () -> a.setOptionUnsafe(LL_OPTS, IsotpLinkLayerOptions.DEFAULT.withMaximumTransmissionUnit(30)));
87+
88+
// FD data length without FD MTU
89+
assertThrows(IllegalArgumentException.class, () -> a.setOption(LL_OPTS, IsotpLinkLayerOptions.DEFAULT.withTransmissionDataLength(48)));
90+
assertThrows(LinuxNativeOperationException.class, () -> a.setOptionUnsafe(LL_OPTS, IsotpLinkLayerOptions.DEFAULT.withTransmissionDataLength(48)));
91+
92+
// tx flags without FD MTU
93+
assertThrows(IllegalArgumentException.class, () -> a.setOption(LL_OPTS, IsotpLinkLayerOptions.DEFAULT.withTransmissionFlags(FD_FLAG_BIT_RATE_SWITCH)));
94+
assertThrows(LinuxNativeOperationException.class, () -> a.setOptionUnsafe(LL_OPTS, IsotpLinkLayerOptions.DEFAULT.withTransmissionFlags(FD_FLAG_BIT_RATE_SWITCH)));
95+
}
96+
}
97+
7698
@Test
7799
void testWriteRead() throws Exception {
78100
IsotpSocketAddress src = isotpAddress(0x7DF);

core/src/main/java/tel/schich/javacan/AbstractCanChannel.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,27 @@ public boolean isBlocking() throws IOException {
142142
* @throws IOException if the native call fails
143143
*/
144144
public <T> void setOption(SocketOption<T> option, T value) throws IOException {
145+
setOption(option, value, true);
146+
}
147+
148+
/**
149+
* Sets a socket option on this {@link java.nio.channels.Channel}'s socket.
150+
*
151+
* @see <a href="https://man7.org/linux/man-pages/man2/setsockopt.2.html">setsockopt man page</a>
152+
* @see <a href="https://man7.org/linux/man-pages/man2/getsockopt.2.html">getsockopt man page</a>
153+
* @param option The option to set
154+
* @param value the value to set
155+
* @param <T> The type of the option
156+
* @throws IOException if the native call fails
157+
*/
158+
public <T> void setOptionUnsafe(SocketOption<T> option, T value) throws IOException {
159+
setOption(option, value, false);
160+
}
161+
162+
private <T> void setOption(SocketOption<T> option, T value, boolean validate) throws IOException {
145163
if (option instanceof CanSocketOption) {
146164
try {
147-
((CanSocketOption<T>) option).getHandler().set(getHandle(), value);
165+
((CanSocketOption<T>) option).getHandler().set(getHandle(), value, validate);
148166
} catch (LinuxNativeOperationException e) {
149167
throw checkForClosedChannel(e);
150168
}

core/src/main/java/tel/schich/javacan/CanSocketOptions.java

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
* This class provides the standard socket options supported by CAN sockets.
3838
*/
3939
public class CanSocketOptions {
40+
public static final int MAX_FILTERS = 512;
4041

4142
private CanSocketOptions() {}
4243

@@ -45,7 +46,7 @@ private CanSocketOptions() {}
4546
*/
4647
public static final SocketOption<Boolean> JOIN_FILTERS = new CanSocketOption<>("JOIN_FILTERS", Boolean.class, new LinuxSocketOptionHandler<Boolean>() {
4748
@Override
48-
public void set(int sock, Boolean val) throws IOException {
49+
public void set(int sock, Boolean val, boolean validate) throws IOException {
4950
SocketCAN.setJoinFilters(sock, val);
5051
}
5152

@@ -61,7 +62,7 @@ public Boolean get(int sock) throws IOException {
6162
*/
6263
public static final SocketOption<Boolean> LOOPBACK = new CanSocketOption<>("LOOPBACK", Boolean.class, new LinuxSocketOptionHandler<Boolean>() {
6364
@Override
64-
public void set(int sock, Boolean val) throws IOException {
65+
public void set(int sock, Boolean val, boolean validate) throws IOException {
6566
SocketCAN.setLoopback(sock, val);
6667
}
6768

@@ -77,7 +78,7 @@ public Boolean get(int sock) throws IOException {
7778
*/
7879
public static final SocketOption<Boolean> RECV_OWN_MSGS = new CanSocketOption<>("RECV_OWN_MSGS", Boolean.class, new LinuxSocketOptionHandler<Boolean>() {
7980
@Override
80-
public void set(int sock, Boolean val) throws IOException {
81+
public void set(int sock, Boolean val, boolean validate) throws IOException {
8182
SocketCAN.setReceiveOwnMessages(sock, val);
8283
}
8384

@@ -93,7 +94,7 @@ public Boolean get(int sock) throws IOException {
9394
*/
9495
public static final SocketOption<Boolean> FD_FRAMES = new CanSocketOption<>("FD_FRAMES", Boolean.class, new LinuxSocketOptionHandler<Boolean>() {
9596
@Override
96-
public void set(int sock, Boolean val) throws IOException {
97+
public void set(int sock, Boolean val, boolean validate) throws IOException {
9798
SocketCAN.setAllowFDFrames(sock, val);
9899
}
99100

@@ -112,7 +113,7 @@ public Boolean get(int sock) throws IOException {
112113
*/
113114
public static final SocketOption<Integer> ERR_FILTER = new CanSocketOption<>("ERR_FILTER", Integer.class, new LinuxSocketOptionHandler<Integer>() {
114115
@Override
115-
public void set(int sock, Integer val) throws IOException {
116+
public void set(int sock, Integer val, boolean validate) throws IOException {
116117
SocketCAN.setErrorFilter(sock, val);
117118
}
118119

@@ -129,8 +130,14 @@ public Integer get(int sock) throws IOException {
129130
* @see <a href="https://man7.org/linux/man-pages/man2/getsockopt.2.html">getsockopt man page</a>
130131
*/
131132
public static final SocketOption<CanFilter[]> FILTER = new CanSocketOption<>("FILTER", CanFilter[].class, new LinuxSocketOptionHandler<CanFilter[]>() {
133+
132134
@Override
133-
public void set(int sock, CanFilter[] val) throws IOException {
135+
public void set(int sock, CanFilter[] val, boolean validate) throws IOException {
136+
if (validate) {
137+
if (val.length > MAX_FILTERS) {
138+
throw new IllegalArgumentException("A maximum of 512 filters are supported!");
139+
}
140+
}
134141
ByteBuffer filterData = JavaCAN.allocateOrdered(val.length * CanFilter.BYTES);
135142
for (CanFilter f : val) {
136143
filterData.putInt(f.getId());
@@ -165,7 +172,7 @@ public CanFilter[] get(int sock) throws IOException {
165172
*/
166173
public static final SocketOption<Duration> SO_SNDTIMEO = new CanSocketOption<>("SO_SNDTIMEO", Duration.class, new LinuxSocketOptionHandler<Duration>() {
167174
@Override
168-
public void set(int sock, Duration val) throws IOException {
175+
public void set(int sock, Duration val, boolean validate) throws IOException {
169176
SocketCAN.setWriteTimeout(sock, val.getSeconds(), val.getNano());
170177
}
171178

@@ -184,7 +191,7 @@ public Duration get(int sock) throws IOException {
184191
*/
185192
public static final SocketOption<Duration> SO_RCVTIMEO = new CanSocketOption<>("SO_RCVTIMEO", Duration.class, new LinuxSocketOptionHandler<Duration>() {
186193
@Override
187-
public void set(int sock, Duration val) throws IOException {
194+
public void set(int sock, Duration val, boolean validate) throws IOException {
188195
SocketCAN.setReadTimeout(sock, val.getSeconds(), val.getNano());
189196
}
190197

@@ -203,7 +210,7 @@ public Duration get(int sock) throws IOException {
203210
*/
204211
public static final SocketOption<Integer> SO_RCVBUF = new CanSocketOption<>("SO_RCVBUF", Integer.class, new LinuxSocketOptionHandler<Integer>() {
205212
@Override
206-
public void set(int sock, Integer val) throws IOException {
213+
public void set(int sock, Integer val, boolean validate) throws IOException {
207214
if (val <= 0) {
208215
throw new IllegalArgumentException("Buffer size must be positive!");
209216
}

core/src/main/java/tel/schich/javacan/IsotpCanSocketOptions.java

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import tel.schich.javacan.platform.linux.LinuxSocketOptionHandler;
2929
import tel.schich.javacan.option.CanSocketOption;
30+
import tel.schich.javacan.util.CanUtils;
3031

3132
/**
3233
* This class provides ISOTP specific socket options supported by CAN sockets. This class is similar to
@@ -44,7 +45,7 @@ private IsotpCanSocketOptions() {}
4445
*/
4546
public static final SocketOption<IsotpOptions> OPTS = new CanSocketOption<>("OPTS", IsotpOptions.class, new LinuxSocketOptionHandler<IsotpOptions>() {
4647
@Override
47-
public void set(int sock, IsotpOptions val) throws IOException {
48+
public void set(int sock, IsotpOptions val, boolean validate) throws IOException {
4849
SocketCAN.setIsotpOpts(sock, val.getRawFlags(), val.getFrameTransmissionTime(), val.getExtendedTransmissionAddress(), val.getTransmissionPadding(), val.getReceivePadding(), val.getExtendedReceiveAddress());
4950
}
5051

@@ -62,7 +63,7 @@ public IsotpOptions get(int sock) throws IOException {
6263
*/
6364
public static final SocketOption<IsotpFlowControlOptions> RECV_FC = new CanSocketOption<>("RECV_FC", IsotpFlowControlOptions.class, new LinuxSocketOptionHandler<IsotpFlowControlOptions>() {
6465
@Override
65-
public void set(int sock, IsotpFlowControlOptions val) throws IOException {
66+
public void set(int sock, IsotpFlowControlOptions val, boolean validate) throws IOException {
6667
SocketCAN.setIsotpRecvFc(sock, val.getBlockSize(), val.getMinimumSeparationTime(), val.getMaximumWaitFrameTransmission());
6768
}
6869

@@ -80,7 +81,7 @@ public IsotpFlowControlOptions get(int sock) throws IOException {
8081
*/
8182
public static final SocketOption<Integer> TX_STMIN = new CanSocketOption<>("TX_STMIN", Integer.class, new LinuxSocketOptionHandler<Integer>() {
8283
@Override
83-
public void set(int sock, Integer val) throws IOException {
84+
public void set(int sock, Integer val, boolean validate) throws IOException {
8485
SocketCAN.setIsotpTxStmin(sock, val);
8586
}
8687

@@ -98,7 +99,7 @@ public Integer get(int sock) throws IOException {
9899
*/
99100
public static final SocketOption<Integer> RX_STMIN = new CanSocketOption<>("RX_STMIN", Integer.class, new LinuxSocketOptionHandler<Integer>() {
100101
@Override
101-
public void set(int sock, Integer val) throws IOException {
102+
public void set(int sock, Integer val, boolean validate) throws IOException {
102103
SocketCAN.setIsotpRxStmin(sock, val);
103104
}
104105

@@ -116,7 +117,31 @@ public Integer get(int sock) throws IOException {
116117
*/
117118
public static final SocketOption<IsotpLinkLayerOptions> LL_OPTS = new CanSocketOption<>("LL_OPTS", IsotpLinkLayerOptions.class, new LinuxSocketOptionHandler<IsotpLinkLayerOptions>() {
118119
@Override
119-
public void set(int sock, IsotpLinkLayerOptions val) throws IOException {
120+
public void set(int sock, IsotpLinkLayerOptions val, boolean validate) throws IOException {
121+
if (validate) {
122+
final int mtu = val.getMaximumTransmissionUnit();
123+
final byte txDataLength = val.getTransmissionDataLength();
124+
125+
// See: https://github.com/torvalds/linux/blob/5f33a09e769a9da0482f20a6770a342842443776/net/can/isotp.c#L1261
126+
if (txDataLength != CanUtils.padDataLength(txDataLength)) {
127+
throw new IllegalArgumentException("The transmission data length must be properly padded!");
128+
}
129+
130+
// See: https://github.com/torvalds/linux/blob/5f33a09e769a9da0482f20a6770a342842443776/net/can/isotp.c#L1264
131+
if (mtu != RawCanChannel.MTU && mtu != RawCanChannel.FD_MTU) {
132+
throw new IllegalArgumentException("MTU must be either " + RawCanChannel.MTU + " or " + RawCanChannel.FD_MTU + "!");
133+
}
134+
135+
// See: https://github.com/torvalds/linux/blob/5f33a09e769a9da0482f20a6770a342842443776/net/can/isotp.c#L1267
136+
if (mtu == RawCanChannel.MTU) {
137+
if (txDataLength > CanFrame.MAX_DATA_LENGTH) {
138+
throw new IllegalArgumentException("Only FD frames support a data length of " + txDataLength + "!");
139+
}
140+
if (val.getTransmissionFlags() != 0) {
141+
throw new IllegalArgumentException("Only FD frames support transmission flags!");
142+
}
143+
}
144+
}
120145
SocketCAN.setIsotpLlOpts(sock, val.getMaximumTransmissionUnit(), val.getTransmissionDataLength(), val.getTransmissionFlags());
121146
}
122147

core/src/main/java/tel/schich/javacan/option/CanSocketOption.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public Handler<T> getHandler() {
6464
* @param <T> the type of the option value
6565
*/
6666
public interface Handler<T> {
67-
void set(UnixFileDescriptor handle, T val) throws IOException;
67+
void set(UnixFileDescriptor handle, T val, boolean validate) throws IOException;
6868
T get(UnixFileDescriptor handle) throws IOException;
6969
}
7070
}

core/src/main/java/tel/schich/javacan/platform/linux/LinuxSocketOptionHandler.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,16 @@
3535
*/
3636
public abstract class LinuxSocketOptionHandler<T> implements CanSocketOption.Handler<T> {
3737
@Override
38-
public void set(UnixFileDescriptor handle, T val) throws IOException {
39-
set(handle.getValue(), val);
38+
public void set(UnixFileDescriptor handle, T val, boolean validate) throws IOException {
39+
set(handle.getValue(), val, validate);
4040
}
4141

4242
@Override
4343
public T get(UnixFileDescriptor handle) throws IOException {
4444
return get(handle.getValue());
4545
}
4646

47-
protected abstract void set(int sock, T val) throws IOException;
47+
protected abstract void set(int sock, T val, boolean validate) throws IOException;
4848

4949
protected abstract T get(int sock) throws IOException;
5050
}

core/src/main/java/tel/schich/javacan/util/CanUtils.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,31 @@ public static String hexDump(ByteBuffer data, int offset, int length) {
4343
}
4444
return s.toString();
4545
}
46+
47+
// see: https://github.com/torvalds/linux/blob/5f33a09e769a9da0482f20a6770a342842443776/net/can/isotp.c#L260
48+
private static final byte[] paddedDataLengthLookup = {
49+
8, 8, 8, 8, 8, 8, 8, 8, 8, /* 0 - 8 */
50+
12, 12, 12, 12, /* 9 - 12 */
51+
16, 16, 16, 16, /* 13 - 16 */
52+
20, 20, 20, 20, /* 17 - 20 */
53+
24, 24, 24, 24, /* 21 - 24 */
54+
32, 32, 32, 32, 32, 32, 32, 32, /* 25 - 32 */
55+
48, 48, 48, 48, 48, 48, 48, 48, /* 33 - 40 */
56+
48, 48, 48, 48, 48, 48, 48, 48 /* 41 - 48 */
57+
};
58+
59+
/**
60+
* Pads a DLC data length value as per ISO 11898-1.
61+
*
62+
* @param length the unpadded length
63+
* @return the padded length
64+
*
65+
* @see <a href="https://github.com/torvalds/linux/blob/5f33a09e769a9da0482f20a6770a342842443776/net/can/isotp.c#L258">Implementation in Linux Kernel</a>
66+
*/
67+
public static byte padDataLength(byte length) {
68+
if (length > 48) {
69+
return 64;
70+
}
71+
return paddedDataLengthLookup[length];
72+
}
4673
}

0 commit comments

Comments
 (0)