diff --git a/README.md b/README.md
index a7bbcb45..f0ac8bef 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,8 @@ Removal of partially implemented code to streamline the lib for just serial port
Full Eclipse integration, for testing application code against sources.
+RFC 2217 support provided by incorporating the jvser library (see http://github.com/archiecobbs/jvser)
+
##And a bunch of bug fixes##
Fixed the memory access error that causes OSX to crash the JVM when serial.close() is called
diff --git a/nrjavaserial/pom.xml b/nrjavaserial/pom.xml
index 5702d92e..322f03eb 100644
--- a/nrjavaserial/pom.xml
+++ b/nrjavaserial/pom.xml
@@ -39,12 +39,23 @@
+
+
+ commons-net
+ commons-net
+ 3.3
+
+
+
+
+ src/main/resources
+
src/main/c/resources/
- **/.svn/
+ **/.svn/
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/AbstractComPortCommandSwitch.java b/nrjavaserial/src/main/java/gnu/io/rfc2217/AbstractComPortCommandSwitch.java
new file mode 100644
index 00000000..b04a1d90
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/AbstractComPortCommandSwitch.java
@@ -0,0 +1,185 @@
+
+/*
+ * Copyright (C) 2010 Archie L. Cobbs. All rights reserved.
+ *
+ * $Id: AbstractComPortCommandSwitch.java 6 2010-11-20 23:37:06Z archie.cobbs $
+ */
+
+package gnu.io.rfc2217;
+
+/**
+ * Adapter class for {@link ComPortCommandSwitch} implementations.
+ *
+ * @see ComPortCommandSwitch
+ * @see ComPortCommand#visit
+ */
+public class AbstractComPortCommandSwitch implements ComPortCommandSwitch {
+
+ /**
+ * Visit method invoked by {@link SignatureCommand} instances.
+ *
+ *
+ * The implementation in {@link AbstractComPortCommandSwitch} delegates to {@link #caseDefault}.
+ *
+ */
+ @Override
+ public void caseSignature(SignatureCommand command) {
+ caseDefault(command);
+ }
+
+ /**
+ * Visit method invoked by {@link BaudRateCommand} instances.
+ *
+ *
+ * The implementation in {@link AbstractComPortCommandSwitch} delegates to {@link #caseDefault}.
+ *
+ */
+ @Override
+ public void caseBaudRate(BaudRateCommand command) {
+ caseDefault(command);
+ }
+
+ /**
+ * Visit method invoked by {@link DataSizeCommand} instances.
+ *
+ *
+ * The implementation in {@link AbstractComPortCommandSwitch} delegates to {@link #caseDefault}.
+ *
+ */
+ @Override
+ public void caseDataSize(DataSizeCommand command) {
+ caseDefault(command);
+ }
+
+ /**
+ * Visit method invoked by {@link ParityCommand} instances.
+ *
+ *
+ * The implementation in {@link AbstractComPortCommandSwitch} delegates to {@link #caseDefault}.
+ *
+ */
+ @Override
+ public void caseParity(ParityCommand command) {
+ caseDefault(command);
+ }
+
+ /**
+ * Visit method invoked by {@link StopSizeCommand} instances.
+ *
+ *
+ * The implementation in {@link AbstractComPortCommandSwitch} delegates to {@link #caseDefault}.
+ *
+ */
+ @Override
+ public void caseStopSize(StopSizeCommand command) {
+ caseDefault(command);
+ }
+
+ /**
+ * Visit method invoked by {@link ControlCommand} instances.
+ *
+ *
+ * The implementation in {@link AbstractComPortCommandSwitch} delegates to {@link #caseDefault}.
+ *
+ */
+ @Override
+ public void caseControl(ControlCommand command) {
+ caseDefault(command);
+ }
+
+ /**
+ * Visit method invoked by {@link NotifyLineStateCommand} instances.
+ *
+ *
+ * The implementation in {@link AbstractComPortCommandSwitch} delegates to {@link #caseDefault}.
+ *
+ */
+ @Override
+ public void caseNotifyLineState(NotifyLineStateCommand command) {
+ caseDefault(command);
+ }
+
+ /**
+ * Visit method invoked by {@link NotifyModemStateCommand} instances.
+ *
+ *
+ * The implementation in {@link AbstractComPortCommandSwitch} delegates to {@link #caseDefault}.
+ *
+ */
+ @Override
+ public void caseNotifyModemState(NotifyModemStateCommand command) {
+ caseDefault(command);
+ }
+
+ /**
+ * Visit method invoked by {@link FlowControlSuspendCommand} instances.
+ *
+ *
+ * The implementation in {@link AbstractComPortCommandSwitch} delegates to {@link #caseDefault}.
+ *
+ */
+ @Override
+ public void caseFlowControlSuspend(FlowControlSuspendCommand command) {
+ caseDefault(command);
+ }
+
+ /**
+ * Visit method invoked by {@link FlowControlResumeCommand} instances.
+ *
+ *
+ * The implementation in {@link AbstractComPortCommandSwitch} delegates to {@link #caseDefault}.
+ *
+ */
+ @Override
+ public void caseFlowControlResume(FlowControlResumeCommand command) {
+ caseDefault(command);
+ }
+
+ /**
+ * Visit method invoked by {@link LineStateMaskCommand} instances.
+ *
+ *
+ * The implementation in {@link AbstractComPortCommandSwitch} delegates to {@link #caseDefault}.
+ *
+ */
+ @Override
+ public void caseLineStateMask(LineStateMaskCommand command) {
+ caseDefault(command);
+ }
+
+ /**
+ * Visit method invoked by {@link ModemStateMaskCommand} instances.
+ *
+ *
+ * The implementation in {@link AbstractComPortCommandSwitch} delegates to {@link #caseDefault}.
+ *
+ */
+ @Override
+ public void caseModemStateMask(ModemStateMaskCommand command) {
+ caseDefault(command);
+ }
+
+ /**
+ * Visit method invoked by {@link PurgeDataCommand} instances.
+ *
+ *
+ * The implementation in {@link AbstractComPortCommandSwitch} delegates to {@link #caseDefault}.
+ *
+ */
+ @Override
+ public void casePurgeData(PurgeDataCommand command) {
+ caseDefault(command);
+ }
+
+ /**
+ * Default handler.
+ *
+ *
+ * All other methods in {@link AbstractComPortCommandSwitch} delegate to this method;
+ * the implementation in {@link AbstractComPortCommandSwitch} does nothing.
+ *
+ */
+ protected void caseDefault(ComPortCommand command) {
+ }
+}
+
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/BaudRateCommand.java b/nrjavaserial/src/main/java/gnu/io/rfc2217/BaudRateCommand.java
new file mode 100644
index 00000000..954021ee
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/BaudRateCommand.java
@@ -0,0 +1,78 @@
+
+/*
+ * Copyright (C) 2010 Archie L. Cobbs. All rights reserved.
+ *
+ * $Id: BaudRateCommand.java 39 2011-03-22 17:21:53Z archie.cobbs $
+ */
+
+package gnu.io.rfc2217;
+
+import static gnu.io.rfc2217.RFC2217.COM_PORT_OPTION;
+import static gnu.io.rfc2217.RFC2217.SERVER_OFFSET;
+import static gnu.io.rfc2217.RFC2217.SET_BAUDRATE;
+
+/**
+ * RFC 2217 {@code SET-BAUDRATE} command.
+ *
+ * @see RFC 2217
+ */
+public class BaudRateCommand extends ComPortCommand {
+
+ private int baudRate;
+
+ /**
+ * Decoding constructor.
+ *
+ * @param bytes encoded option starting with the {@code COM-PORT-OPTION} byte
+ * @throws NullPointerException if {@code bytes} is null
+ * @throws IllegalArgumentException if {@code bytes} has length that is too short or too long
+ * @throws IllegalArgumentException if {@code bytes[0]} is not {@link RFC2217#COM_PORT_OPTION}
+ * @throws IllegalArgumentException if {@code bytes[1]} is not {@link RFC2217#SET_BAUDRATE} (client or server)
+ */
+ public BaudRateCommand(int[] bytes) {
+ super("SET-BAUDRATE", SET_BAUDRATE, bytes);
+ this.baudRate = ((bytes[2] & 0xff) << 24) | ((bytes[3] & 0xff) << 16) | ((bytes[4] & 0xff) << 8) | (bytes[5] & 0xff);
+ }
+
+ /**
+ * Encoding constructor.
+ *
+ * @param baudRate baud rate
+ * @param client true for the client-to-server command, false for the server-to-client command
+ */
+ public BaudRateCommand(boolean client, int baudRate) {
+ this(new int[] {
+ COM_PORT_OPTION,
+ client ? SET_BAUDRATE : SET_BAUDRATE + SERVER_OFFSET,
+ (baudRate >> 24) & 0xff,
+ (baudRate >> 16) & 0xff,
+ (baudRate >> 8) & 0xff,
+ baudRate & 0xff,
+ });
+ }
+
+ @Override
+ public String toString() {
+ return this.getName() + " " + this.baudRate;
+ }
+
+ @Override
+ public void visit(ComPortCommandSwitch sw) {
+ sw.caseBaudRate(this);
+ }
+
+ public int getBaudRate() {
+ return this.baudRate;
+ }
+
+ @Override
+ int getMinPayloadLength() {
+ return 4;
+ }
+
+ @Override
+ int getMaxPayloadLength() {
+ return 4;
+ }
+}
+
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/ComPortCommand.java b/nrjavaserial/src/main/java/gnu/io/rfc2217/ComPortCommand.java
new file mode 100644
index 00000000..e96a2f9b
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/ComPortCommand.java
@@ -0,0 +1,142 @@
+
+/*
+ * Copyright (C) 2010 Archie L. Cobbs. All rights reserved.
+ *
+ * $Id: ComPortCommand.java 47 2011-12-07 15:56:28Z archie.cobbs $
+ */
+
+package gnu.io.rfc2217;
+
+import java.util.Arrays;
+
+import static gnu.io.rfc2217.RFC2217.COM_PORT_OPTION;
+import static gnu.io.rfc2217.RFC2217.SERVER_OFFSET;
+
+/**
+ * Superclass for RFC 2217 commands.
+ *
+ *
+ * Instances of this class (and all subclasses) are immutable.
+ *
+ *
+ * @see RFC 2217
+ */
+public abstract class ComPortCommand {
+
+ final String name;
+ final int[] bytes;
+
+ /**
+ * Constructor.
+ *
+ * @param name human readable name of this command
+ * @param command required {@code COM-PORT-OPTION} command byte value (must be the client-to-server value)
+ * @param bytes encoded command starting with the {@code COM-PORT-OPTION} byte
+ * @throws NullPointerException if {@code bytes} is null
+ * @throws IllegalArgumentException if {@code bytes} has length that is too short or too long
+ * @throws IllegalArgumentException if {@code bytes[0]} is not {@link RFC2217#COM_PORT_OPTION}
+ * @throws IllegalArgumentException if {@code command} is greater than or equal to {@link RFC2217#SERVER_OFFSET}
+ * @throws IllegalArgumentException if {@code bytes[1]} is not {@code command}
+ * or {@code command} + {@link RFC2217#SERVER_OFFSET}
+ */
+ protected ComPortCommand(String name, int command, int[] bytes) {
+ this.name = name;
+ int minLength = 2 + this.getMinPayloadLength();
+ int maxLength = 2 + this.getMaxPayloadLength();
+ if (bytes.length < minLength || bytes.length > maxLength) {
+ throw new IllegalArgumentException("command " + command + " length = "
+ + bytes.length + " is not in the range " + minLength + ".." + maxLength);
+ }
+ this.bytes = bytes.clone(); // maintain immutability
+ if (this.bytes[0] != COM_PORT_OPTION)
+ throw new IllegalArgumentException("not a COM-PORT-OPTION");
+ if (command >= SERVER_OFFSET)
+ throw new IllegalArgumentException("invalid command " + command);
+ if (this.getCommand() != command && this.getCommand() != command + SERVER_OFFSET)
+ throw new IllegalArgumentException("not a " + name + " option");
+ }
+
+ /**
+ * Determine if this command is client-to-server or server-to-client.
+ *
+ * @return true if this command is sent from the server to the client, false for the opposite
+ */
+ public final boolean isServerCommand() {
+ return this.getCommand() >= SERVER_OFFSET;
+ }
+
+ /**
+ * Get the encoding of this instance.
+ *
+ * @return encoding starting with {@code COM-PORT-OPTION}
+ */
+ public final int[] getBytes() {
+ return this.bytes.clone(); // maintain immutability
+ }
+
+ /**
+ * Get the command byte.
+ *
+ * @return RFC 2217-defined byte value for this command
+ */
+ public final int getCommand() {
+ return this.bytes[1] & 0xff;
+ }
+
+ /**
+ * Get the human-readable name of this option.
+ */
+ public String getName() {
+ return this.name + (this.isServerCommand() ? "[S]" : "[C]");
+ }
+
+ /**
+ * Get the human-readable description of this option.
+ */
+ @Override
+ public abstract String toString();
+
+ /**
+ * Apply visitor pattern.
+ *
+ * @param sw visitor switch handler
+ */
+ public abstract void visit(ComPortCommandSwitch sw);
+
+ /**
+ * Get the option payload as bytes.
+ */
+ byte[] getPayload() {
+ byte[] buf = new byte[this.bytes.length - 2];
+ for (int i = 2; i < bytes.length; i++)
+ buf[i - 2] = (byte)this.bytes[i];
+ return buf;
+ }
+
+ /**
+ * Get minimum required length of the payload of this command.
+ */
+ abstract int getMinPayloadLength();
+
+ /**
+ * Get maximum required length of the payload of this command.
+ */
+ abstract int getMaxPayloadLength();
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || obj.getClass() != getClass())
+ return false;
+ ComPortCommand that = (ComPortCommand)obj;
+ return Arrays.equals(this.bytes, that.bytes);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ for (int i = 0; i < this.bytes.length; i++)
+ hash = hash * 37 + this.bytes[i];
+ return hash;
+ }
+}
+
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/ComPortCommandSwitch.java b/nrjavaserial/src/main/java/gnu/io/rfc2217/ComPortCommandSwitch.java
new file mode 100644
index 00000000..0eff667a
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/ComPortCommandSwitch.java
@@ -0,0 +1,43 @@
+
+/*
+ * Copyright (C) 2010 Archie L. Cobbs. All rights reserved.
+ *
+ * $Id: ComPortCommandSwitch.java 6 2010-11-20 23:37:06Z archie.cobbs $
+ */
+
+package gnu.io.rfc2217;
+
+/**
+ * Visitor pattern interface for {@link ComPortCommand} classes.
+ *
+ * @see ComPortCommand#visit
+ */
+public interface ComPortCommandSwitch {
+
+ void caseSignature(SignatureCommand command);
+
+ void caseBaudRate(BaudRateCommand command);
+
+ void caseDataSize(DataSizeCommand command);
+
+ void caseParity(ParityCommand command);
+
+ void caseStopSize(StopSizeCommand command);
+
+ void caseControl(ControlCommand command);
+
+ void caseNotifyLineState(NotifyLineStateCommand command);
+
+ void caseNotifyModemState(NotifyModemStateCommand command);
+
+ void caseFlowControlSuspend(FlowControlSuspendCommand command);
+
+ void caseFlowControlResume(FlowControlResumeCommand command);
+
+ void caseLineStateMask(LineStateMaskCommand command);
+
+ void caseModemStateMask(ModemStateMaskCommand command);
+
+ void casePurgeData(PurgeDataCommand command);
+}
+
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/ComPortOptionHandler.java b/nrjavaserial/src/main/java/gnu/io/rfc2217/ComPortOptionHandler.java
new file mode 100644
index 00000000..acf1b709
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/ComPortOptionHandler.java
@@ -0,0 +1,63 @@
+
+/*
+ * Copyright (C) 2010 Archie L. Cobbs. All rights reserved.
+ *
+ * $Id: ComPortOptionHandler.java 46 2011-10-16 23:06:39Z archie.cobbs $
+ */
+
+package gnu.io.rfc2217;
+
+import org.apache.commons.net.telnet.TelnetOptionHandler;
+
+/**
+ * RFC 2217 telnet COM-PORT-OPTION.
+ *
+ * @see RFC 2217
+ */
+public class ComPortOptionHandler extends TelnetOptionHandler {
+
+ private final TelnetSerialPort port;
+
+ protected ComPortOptionHandler(TelnetSerialPort telnetSerialPort) {
+ super(RFC2217.COM_PORT_OPTION, true, false, true, false);
+ if (telnetSerialPort == null)
+ throw new IllegalArgumentException("null telnetSerialPort");
+ this.port = telnetSerialPort;
+ }
+
+ @Override
+ public int[] answerSubnegotiation(int[] data, int length) {
+
+ // Copy data into buffer of the correct size
+ if (data.length != length) {
+ int[] data2 = new int[length];
+ System.arraycopy(data, 0, data2, 0, length);
+ data = data2;
+ }
+
+ // Decode option
+ ComPortCommand command;
+ try {
+ command = RFC2217.decodeComPortCommand(data);
+ } catch (IllegalArgumentException e) {
+ System.err.println(this.port.getName() + ": rec'd invalid COM-PORT-OPTION command: " + e.getMessage());
+ return null;
+ }
+
+ // Notify port
+ this.port.handleCommand(command);
+ return null;
+ }
+
+ @Override
+ public int[] startSubnegotiationLocal() {
+ this.port.startSubnegotiation();
+ return null;
+ }
+
+ @Override
+ public int[] startSubnegotiationRemote() {
+ return null;
+ }
+}
+
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/ControlCommand.java b/nrjavaserial/src/main/java/gnu/io/rfc2217/ControlCommand.java
new file mode 100644
index 00000000..50756c36
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/ControlCommand.java
@@ -0,0 +1,188 @@
+
+/*
+ * Copyright (C) 2010 Archie L. Cobbs. All rights reserved.
+ *
+ * $Id: ControlCommand.java 39 2011-03-22 17:21:53Z archie.cobbs $
+ */
+
+package gnu.io.rfc2217;
+
+import static gnu.io.rfc2217.RFC2217.COM_PORT_OPTION;
+import static gnu.io.rfc2217.RFC2217.CONTROL_BREAK_OFF;
+import static gnu.io.rfc2217.RFC2217.CONTROL_BREAK_ON;
+import static gnu.io.rfc2217.RFC2217.CONTROL_BREAK_REQUEST;
+import static gnu.io.rfc2217.RFC2217.CONTROL_DTR_OFF;
+import static gnu.io.rfc2217.RFC2217.CONTROL_DTR_ON;
+import static gnu.io.rfc2217.RFC2217.CONTROL_DTR_REQUEST;
+import static gnu.io.rfc2217.RFC2217.CONTROL_INBOUND_FLOW_DTR;
+import static gnu.io.rfc2217.RFC2217.CONTROL_INBOUND_FLOW_HARDWARE;
+import static gnu.io.rfc2217.RFC2217.CONTROL_INBOUND_FLOW_NONE;
+import static gnu.io.rfc2217.RFC2217.CONTROL_INBOUND_FLOW_REQUEST;
+import static gnu.io.rfc2217.RFC2217.CONTROL_INBOUND_FLOW_XON_XOFF;
+import static gnu.io.rfc2217.RFC2217.CONTROL_OUTBOUND_FLOW_DCD;
+import static gnu.io.rfc2217.RFC2217.CONTROL_OUTBOUND_FLOW_DSR;
+import static gnu.io.rfc2217.RFC2217.CONTROL_OUTBOUND_FLOW_HARDWARE;
+import static gnu.io.rfc2217.RFC2217.CONTROL_OUTBOUND_FLOW_NONE;
+import static gnu.io.rfc2217.RFC2217.CONTROL_OUTBOUND_FLOW_REQUEST;
+import static gnu.io.rfc2217.RFC2217.CONTROL_OUTBOUND_FLOW_XON_XOFF;
+import static gnu.io.rfc2217.RFC2217.CONTROL_RTS_OFF;
+import static gnu.io.rfc2217.RFC2217.CONTROL_RTS_ON;
+import static gnu.io.rfc2217.RFC2217.CONTROL_RTS_REQUEST;
+import static gnu.io.rfc2217.RFC2217.SERVER_OFFSET;
+import static gnu.io.rfc2217.RFC2217.SET_CONTROL;
+
+/**
+ * RFC 2217 {@code SET-CONTROL} command.
+ *
+ * @see RFC 2217
+ */
+public class ControlCommand extends ComPortCommand {
+
+ private int control;
+
+ /**
+ * Decoding constructor.
+ *
+ * @param bytes encoded option starting with the {@code COM-PORT-OPTION} byte
+ * @throws NullPointerException if {@code bytes} is null
+ * @throws IllegalArgumentException if {@code bytes} has length != 3
+ * @throws IllegalArgumentException if {@code bytes[0]} is not {@link RFC2217#COM_PORT_OPTION}
+ * @throws IllegalArgumentException if {@code bytes[1]} is not {@link RFC2217#SET_CONTROL} (client or server)
+ * @throws IllegalArgumentException if {@code bytes[2]} is not a valid RFC 2217 control value
+ */
+ public ControlCommand(int[] bytes) {
+ super("SET-CONTROL", SET_CONTROL, bytes);
+ this.control = bytes[2];
+ switch (this.control) {
+ case CONTROL_OUTBOUND_FLOW_REQUEST:
+ case CONTROL_OUTBOUND_FLOW_NONE:
+ case CONTROL_OUTBOUND_FLOW_XON_XOFF:
+ case CONTROL_OUTBOUND_FLOW_HARDWARE:
+ case CONTROL_BREAK_REQUEST:
+ case CONTROL_BREAK_ON:
+ case CONTROL_BREAK_OFF:
+ case CONTROL_DTR_REQUEST:
+ case CONTROL_DTR_ON:
+ case CONTROL_DTR_OFF:
+ case CONTROL_RTS_REQUEST:
+ case CONTROL_RTS_ON:
+ case CONTROL_RTS_OFF:
+ case CONTROL_INBOUND_FLOW_REQUEST:
+ case CONTROL_INBOUND_FLOW_NONE:
+ case CONTROL_INBOUND_FLOW_XON_XOFF:
+ case CONTROL_INBOUND_FLOW_HARDWARE:
+ case CONTROL_OUTBOUND_FLOW_DCD:
+ case CONTROL_INBOUND_FLOW_DTR:
+ case CONTROL_OUTBOUND_FLOW_DSR:
+ break;
+ default:
+ throw new IllegalArgumentException("invalid control value " + this.control);
+ }
+ }
+
+ /**
+ * Encoding constructor.
+ *
+ * @param command control command
+ * @param client true for the client-to-server command, false for the server-to-client command
+ * @throws IllegalArgumentException if {@code command} is not a valid RFC 2217 control value
+ */
+ public ControlCommand(boolean client, int command) {
+ this(new int[] {
+ COM_PORT_OPTION,
+ client ? SET_CONTROL : SET_CONTROL + SERVER_OFFSET,
+ command
+ });
+ }
+
+ @Override
+ public String toString() {
+ String desc;
+ switch (this.control) {
+ case CONTROL_OUTBOUND_FLOW_REQUEST:
+ desc = "OUTBOUND-FLOW-REQUEST";
+ break;
+ case CONTROL_OUTBOUND_FLOW_NONE:
+ desc = "OUTBOUND-FLOW-NONE";
+ break;
+ case CONTROL_OUTBOUND_FLOW_XON_XOFF:
+ desc = "OUTBOUND-FLOW-XON-XOFF";
+ break;
+ case CONTROL_OUTBOUND_FLOW_HARDWARE:
+ desc = "OUTBOUND-FLOW-HARDWARE";
+ break;
+ case CONTROL_BREAK_REQUEST:
+ desc = "OUTBOUND-BREAK-REQUEST";
+ break;
+ case CONTROL_BREAK_ON:
+ desc = "OUTBOUND-BREAK-ON";
+ break;
+ case CONTROL_BREAK_OFF:
+ desc = "OUTBOUND-BREAK-OFF";
+ break;
+ case CONTROL_DTR_REQUEST:
+ desc = "OUTBOUND-DTR-REQUEST";
+ break;
+ case CONTROL_DTR_ON:
+ desc = "OUTBOUND-DTR-ON";
+ break;
+ case CONTROL_DTR_OFF:
+ desc = "OUTBOUND-DTR-OFF";
+ break;
+ case CONTROL_RTS_REQUEST:
+ desc = "OUTBOUND-RTS-REQUEST";
+ break;
+ case CONTROL_RTS_ON:
+ desc = "OUTBOUND-RTS-ON";
+ break;
+ case CONTROL_RTS_OFF:
+ desc = "OUTBOUND-RTS-OFF";
+ break;
+ case CONTROL_INBOUND_FLOW_REQUEST:
+ desc = "INBOUND-FLOW-REQUEST";
+ break;
+ case CONTROL_INBOUND_FLOW_NONE:
+ desc = "INBOUND-FLOW-NONE";
+ break;
+ case CONTROL_INBOUND_FLOW_XON_XOFF:
+ desc = "INBOUND-FLOW-XON-OFF";
+ break;
+ case CONTROL_INBOUND_FLOW_HARDWARE:
+ desc = "INBOUND-FLOW-HARDWARE";
+ break;
+ case CONTROL_OUTBOUND_FLOW_DCD:
+ desc = "OUTBOUND-FLOW-DCD";
+ break;
+ case CONTROL_INBOUND_FLOW_DTR:
+ desc = "INBOUND-FLOW-DTR";
+ break;
+ case CONTROL_OUTBOUND_FLOW_DSR:
+ desc = "OUTBOUND-FLOW-DSR";
+ break;
+ default:
+ desc = "?";
+ break;
+ }
+ return this.getName() + " " + desc;
+ }
+
+ @Override
+ public void visit(ComPortCommandSwitch sw) {
+ sw.caseControl(this);
+ }
+
+ public int getControl() {
+ return this.control;
+ }
+
+ @Override
+ int getMinPayloadLength() {
+ return 1;
+ }
+
+ @Override
+ int getMaxPayloadLength() {
+ return 1;
+ }
+}
+
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/DataSizeCommand.java b/nrjavaserial/src/main/java/gnu/io/rfc2217/DataSizeCommand.java
new file mode 100644
index 00000000..9af103c7
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/DataSizeCommand.java
@@ -0,0 +1,113 @@
+
+/*
+ * Copyright (C) 2010 Archie L. Cobbs. All rights reserved.
+ *
+ * $Id: DataSizeCommand.java 39 2011-03-22 17:21:53Z archie.cobbs $
+ */
+
+package gnu.io.rfc2217;
+
+import static gnu.io.rfc2217.RFC2217.COM_PORT_OPTION;
+import static gnu.io.rfc2217.RFC2217.DATASIZE_5;
+import static gnu.io.rfc2217.RFC2217.DATASIZE_6;
+import static gnu.io.rfc2217.RFC2217.DATASIZE_7;
+import static gnu.io.rfc2217.RFC2217.DATASIZE_8;
+import static gnu.io.rfc2217.RFC2217.DATASIZE_REQUEST;
+import static gnu.io.rfc2217.RFC2217.SERVER_OFFSET;
+import static gnu.io.rfc2217.RFC2217.SET_DATASIZE;
+
+/**
+ * RFC 2217 {@code SET-DATASIZE} command.
+ *
+ * @see RFC 2217
+ */
+public class DataSizeCommand extends ComPortCommand {
+
+ private int dataSize;
+
+ /**
+ * Decoding constructor.
+ *
+ * @param bytes encoded option starting with the {@code COM-PORT-OPTION} byte
+ * @throws NullPointerException if {@code bytes} is null
+ * @throws IllegalArgumentException if {@code bytes} has length != 3
+ * @throws IllegalArgumentException if {@code bytes[0]} is not {@link RFC2217#COM_PORT_OPTION}
+ * @throws IllegalArgumentException if {@code bytes[1]} is not {@link RFC2217#SET_DATASIZE} (client or server)
+ * @throws IllegalArgumentException if {@code bytes[2]} is not a valid RFC 2217 data size value
+ */
+ public DataSizeCommand(int[] bytes) {
+ super("SET-DATASIZE", SET_DATASIZE, bytes);
+ this.dataSize = bytes[2];
+ switch (this.dataSize) {
+ case DATASIZE_REQUEST:
+ case DATASIZE_5:
+ case DATASIZE_6:
+ case DATASIZE_7:
+ case DATASIZE_8:
+ break;
+ default:
+ throw new IllegalArgumentException("invalid data size value " + this.dataSize);
+ }
+ }
+
+ /**
+ * Encoding constructor.
+ *
+ * @param dataSize data size value
+ * @param client true for the client-to-server command, false for the server-to-client command
+ * @throws IllegalArgumentException if {@code dataSize} is not a valid RFC 2217 data size value
+ */
+ public DataSizeCommand(boolean client, int dataSize) {
+ this(new int[] {
+ COM_PORT_OPTION,
+ client ? SET_DATASIZE : SET_DATASIZE + SERVER_OFFSET,
+ dataSize
+ });
+ }
+
+ @Override
+ public String toString() {
+ String desc;
+ switch (this.dataSize) {
+ case DATASIZE_REQUEST:
+ desc = "REQUEST";
+ break;
+ case DATASIZE_5:
+ desc = "5";
+ break;
+ case DATASIZE_6:
+ desc = "6";
+ break;
+ case DATASIZE_7:
+ desc = "7";
+ break;
+ case DATASIZE_8:
+ desc = "8";
+ break;
+ default:
+ desc = "?";
+ break;
+ }
+ return this.getName() + " " + desc;
+ }
+
+ @Override
+ public void visit(ComPortCommandSwitch sw) {
+ sw.caseDataSize(this);
+ }
+
+ public int getDataSize() {
+ return this.dataSize;
+ }
+
+ @Override
+ int getMinPayloadLength() {
+ return 1;
+ }
+
+ @Override
+ int getMaxPayloadLength() {
+ return 1;
+ }
+}
+
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/FlowControlResumeCommand.java b/nrjavaserial/src/main/java/gnu/io/rfc2217/FlowControlResumeCommand.java
new file mode 100644
index 00000000..153e8493
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/FlowControlResumeCommand.java
@@ -0,0 +1,66 @@
+
+/*
+ * Copyright (C) 2010 Archie L. Cobbs. All rights reserved.
+ *
+ * $Id: FlowControlResumeCommand.java 39 2011-03-22 17:21:53Z archie.cobbs $
+ */
+
+package gnu.io.rfc2217;
+
+import static gnu.io.rfc2217.RFC2217.COM_PORT_OPTION;
+import static gnu.io.rfc2217.RFC2217.FLOWCONTROL_RESUME;
+import static gnu.io.rfc2217.RFC2217.SERVER_OFFSET;
+
+/**
+ * RFC 2217 {@code FLOWCONTROL-RESUME} command.
+ *
+ * @see RFC 2217
+ */
+public class FlowControlResumeCommand extends ComPortCommand {
+
+ /**
+ * Decoding constructor.
+ *
+ * @param bytes encoded option starting with the {@code COM-PORT-OPTION} byte
+ * @throws NullPointerException if {@code bytes} is null
+ * @throws IllegalArgumentException if {@code bytes} has length != 3
+ * @throws IllegalArgumentException if {@code bytes[0]} is not {@link RFC2217#COM_PORT_OPTION}
+ * @throws IllegalArgumentException if {@code bytes[1]} is not {@link RFC2217#FLOWCONTROL_RESUME} (client or server)
+ */
+ public FlowControlResumeCommand(int[] bytes) {
+ super("FLOWCONTROL-RESUME", FLOWCONTROL_RESUME, bytes);
+ }
+
+ /**
+ * Encoding constructor.
+ *
+ * @param client true for the client-to-server command, false for the server-to-client command
+ */
+ public FlowControlResumeCommand(boolean client) {
+ this(new int[] {
+ COM_PORT_OPTION,
+ client ? FLOWCONTROL_RESUME : FLOWCONTROL_RESUME + SERVER_OFFSET,
+ });
+ }
+
+ @Override
+ public String toString() {
+ return this.getName();
+ }
+
+ @Override
+ public void visit(ComPortCommandSwitch sw) {
+ sw.caseFlowControlResume(this);
+ }
+
+ @Override
+ int getMinPayloadLength() {
+ return 0;
+ }
+
+ @Override
+ int getMaxPayloadLength() {
+ return 0;
+ }
+}
+
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/FlowControlSuspendCommand.java b/nrjavaserial/src/main/java/gnu/io/rfc2217/FlowControlSuspendCommand.java
new file mode 100644
index 00000000..558fe9c4
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/FlowControlSuspendCommand.java
@@ -0,0 +1,66 @@
+
+/*
+ * Copyright (C) 2010 Archie L. Cobbs. All rights reserved.
+ *
+ * $Id: FlowControlSuspendCommand.java 39 2011-03-22 17:21:53Z archie.cobbs $
+ */
+
+package gnu.io.rfc2217;
+
+import static gnu.io.rfc2217.RFC2217.COM_PORT_OPTION;
+import static gnu.io.rfc2217.RFC2217.FLOWCONTROL_SUSPEND;
+import static gnu.io.rfc2217.RFC2217.SERVER_OFFSET;
+
+/**
+ * RFC 2217 {@code FLOWCONTROL-SUSPEND} command.
+ *
+ * @see RFC 2217
+ */
+public class FlowControlSuspendCommand extends ComPortCommand {
+
+ /**
+ * Decoding constructor.
+ *
+ * @param bytes encoded option starting with the {@code COM-PORT-OPTION} byte
+ * @throws NullPointerException if {@code bytes} is null
+ * @throws IllegalArgumentException if {@code bytes} has length != 3
+ * @throws IllegalArgumentException if {@code bytes[0]} is not {@link RFC2217#COM_PORT_OPTION}
+ * @throws IllegalArgumentException if {@code bytes[1]} is not {@link RFC2217#FLOWCONTROL_SUSPEND} (client or server)
+ */
+ public FlowControlSuspendCommand(int[] bytes) {
+ super("FLOWCONTROL-SUSPEND", FLOWCONTROL_SUSPEND, bytes);
+ }
+
+ /**
+ * Encoding constructor.
+ *
+ * @param client true for the client-to-server command, false for the server-to-client command
+ */
+ public FlowControlSuspendCommand(boolean client) {
+ this(new int[] {
+ COM_PORT_OPTION,
+ client ? FLOWCONTROL_SUSPEND : FLOWCONTROL_SUSPEND + SERVER_OFFSET,
+ });
+ }
+
+ @Override
+ public String toString() {
+ return this.getName();
+ }
+
+ @Override
+ public void visit(ComPortCommandSwitch sw) {
+ sw.caseFlowControlSuspend(this);
+ }
+
+ @Override
+ int getMinPayloadLength() {
+ return 0;
+ }
+
+ @Override
+ int getMaxPayloadLength() {
+ return 0;
+ }
+}
+
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/LineStateMaskCommand.java b/nrjavaserial/src/main/java/gnu/io/rfc2217/LineStateMaskCommand.java
new file mode 100644
index 00000000..d1a03443
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/LineStateMaskCommand.java
@@ -0,0 +1,75 @@
+
+/*
+ * Copyright (C) 2010 Archie L. Cobbs. All rights reserved.
+ *
+ * $Id: LineStateMaskCommand.java 39 2011-03-22 17:21:53Z archie.cobbs $
+ */
+
+package gnu.io.rfc2217;
+
+import static gnu.io.rfc2217.RFC2217.COM_PORT_OPTION;
+import static gnu.io.rfc2217.RFC2217.SERVER_OFFSET;
+import static gnu.io.rfc2217.RFC2217.SET_LINESTATE_MASK;
+
+/**
+ * RFC 2217 {@code SET-LINESTATE-MASK} command.
+ *
+ * @see RFC 2217
+ */
+public class LineStateMaskCommand extends ComPortCommand {
+
+ private int lineStateMask;
+
+ /**
+ * Decoding constructor.
+ *
+ * @param bytes encoded option starting with the {@code COM-PORT-OPTION} byte
+ * @throws NullPointerException if {@code bytes} is null
+ * @throws IllegalArgumentException if {@code bytes} has length != 3
+ * @throws IllegalArgumentException if {@code bytes[0]} is not {@link RFC2217#COM_PORT_OPTION}
+ * @throws IllegalArgumentException if {@code bytes[1]} is not {@link RFC2217#SET_LINESTATE_MASK} (client or server)
+ */
+ public LineStateMaskCommand(int[] bytes) {
+ super("SET-LINESTATE-MASK", SET_LINESTATE_MASK, bytes);
+ this.lineStateMask = bytes[2];
+ }
+
+ /**
+ * Encoding constructor.
+ *
+ * @param lineStateMask line state mask value
+ * @param client true for the client-to-server command, false for the server-to-client command
+ */
+ public LineStateMaskCommand(boolean client, int lineStateMask) {
+ this(new int[] {
+ COM_PORT_OPTION,
+ client ? SET_LINESTATE_MASK : SET_LINESTATE_MASK + SERVER_OFFSET,
+ lineStateMask
+ });
+ }
+
+ @Override
+ public String toString() {
+ return this.getName() + " " + Util.decodeBits(this.lineStateMask, Util.LINE_STATE_BITS);
+ }
+
+ @Override
+ public void visit(ComPortCommandSwitch sw) {
+ sw.caseLineStateMask(this);
+ }
+
+ public int getLineStateMask() {
+ return this.lineStateMask;
+ }
+
+ @Override
+ int getMinPayloadLength() {
+ return 1;
+ }
+
+ @Override
+ int getMaxPayloadLength() {
+ return 1;
+ }
+}
+
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/ModemStateMaskCommand.java b/nrjavaserial/src/main/java/gnu/io/rfc2217/ModemStateMaskCommand.java
new file mode 100644
index 00000000..820b248e
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/ModemStateMaskCommand.java
@@ -0,0 +1,75 @@
+
+/*
+ * Copyright (C) 2010 Archie L. Cobbs. All rights reserved.
+ *
+ * $Id: ModemStateMaskCommand.java 39 2011-03-22 17:21:53Z archie.cobbs $
+ */
+
+package gnu.io.rfc2217;
+
+import static gnu.io.rfc2217.RFC2217.COM_PORT_OPTION;
+import static gnu.io.rfc2217.RFC2217.SERVER_OFFSET;
+import static gnu.io.rfc2217.RFC2217.SET_MODEMSTATE_MASK;
+
+/**
+ * RFC 2217 {@code SET-MODEMSTATE-MASK} command.
+ *
+ * @see RFC 2217
+ */
+public class ModemStateMaskCommand extends ComPortCommand {
+
+ private int modemStateMask;
+
+ /**
+ * Decoding constructor.
+ *
+ * @param bytes encoded option starting with the {@code COM-PORT-OPTION} byte
+ * @throws NullPointerException if {@code bytes} is null
+ * @throws IllegalArgumentException if {@code bytes} has length != 3
+ * @throws IllegalArgumentException if {@code bytes[0]} is not {@link RFC2217#COM_PORT_OPTION}
+ * @throws IllegalArgumentException if {@code bytes[1]} is not {@link RFC2217#SET_MODEMSTATE_MASK} (client or server)
+ */
+ public ModemStateMaskCommand(int[] bytes) {
+ super("SET-MODEMSTATE-MASK", SET_MODEMSTATE_MASK, bytes);
+ this.modemStateMask = bytes[2];
+ }
+
+ /**
+ * Encoding constructor.
+ *
+ * @param modemStateMask modem state mask value
+ * @param client true for the client-to-server command, false for the server-to-client command
+ */
+ public ModemStateMaskCommand(boolean client, int modemStateMask) {
+ this(new int[] {
+ COM_PORT_OPTION,
+ client ? SET_MODEMSTATE_MASK : SET_MODEMSTATE_MASK + SERVER_OFFSET,
+ modemStateMask
+ });
+ }
+
+ @Override
+ public String toString() {
+ return this.getName() + " " + Util.decodeBits(this.modemStateMask, Util.MODEM_STATE_BITS);
+ }
+
+ @Override
+ public void visit(ComPortCommandSwitch sw) {
+ sw.caseModemStateMask(this);
+ }
+
+ public int getModemStateMask() {
+ return this.modemStateMask;
+ }
+
+ @Override
+ int getMinPayloadLength() {
+ return 1;
+ }
+
+ @Override
+ int getMaxPayloadLength() {
+ return 1;
+ }
+}
+
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/NotifyLineStateCommand.java b/nrjavaserial/src/main/java/gnu/io/rfc2217/NotifyLineStateCommand.java
new file mode 100644
index 00000000..4609b723
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/NotifyLineStateCommand.java
@@ -0,0 +1,75 @@
+
+/*
+ * Copyright (C) 2010 Archie L. Cobbs. All rights reserved.
+ *
+ * $Id: NotifyLineStateCommand.java 39 2011-03-22 17:21:53Z archie.cobbs $
+ */
+
+package gnu.io.rfc2217;
+
+import static gnu.io.rfc2217.RFC2217.COM_PORT_OPTION;
+import static gnu.io.rfc2217.RFC2217.NOTIFY_LINESTATE;
+import static gnu.io.rfc2217.RFC2217.SERVER_OFFSET;
+
+/**
+ * RFC 2217 {@code NOTIFY-LINESTATE} command.
+ *
+ * @see RFC 2217
+ */
+public class NotifyLineStateCommand extends ComPortCommand {
+
+ private int lineState;
+
+ /**
+ * Decoding constructor.
+ *
+ * @param bytes encoded option starting with the {@code COM-PORT-OPTION} byte
+ * @throws NullPointerException if {@code bytes} is null
+ * @throws IllegalArgumentException if {@code bytes} has length != 3
+ * @throws IllegalArgumentException if {@code bytes[0]} is not {@link RFC2217#COM_PORT_OPTION}
+ * @throws IllegalArgumentException if {@code bytes[1]} is not {@link RFC2217#NOTIFY_LINESTATE} (client or server)
+ */
+ public NotifyLineStateCommand(int[] bytes) {
+ super("NOTIFY-LINESTATE", NOTIFY_LINESTATE, bytes);
+ this.lineState = bytes[2];
+ }
+
+ /**
+ * Encoding constructor.
+ *
+ * @param lineState line state value
+ * @param client true for the client-to-server command, false for the server-to-client command
+ */
+ public NotifyLineStateCommand(boolean client, int lineState) {
+ this(new int[] {
+ COM_PORT_OPTION,
+ client ? NOTIFY_LINESTATE : NOTIFY_LINESTATE + SERVER_OFFSET,
+ lineState
+ });
+ }
+
+ @Override
+ public String toString() {
+ return this.getName() + " " + Util.decodeBits(this.lineState, Util.LINE_STATE_BITS);
+ }
+
+ @Override
+ public void visit(ComPortCommandSwitch sw) {
+ sw.caseNotifyLineState(this);
+ }
+
+ public int getLineState() {
+ return this.lineState;
+ }
+
+ @Override
+ int getMinPayloadLength() {
+ return 1;
+ }
+
+ @Override
+ int getMaxPayloadLength() {
+ return 1;
+ }
+}
+
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/NotifyModemStateCommand.java b/nrjavaserial/src/main/java/gnu/io/rfc2217/NotifyModemStateCommand.java
new file mode 100644
index 00000000..801c15f8
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/NotifyModemStateCommand.java
@@ -0,0 +1,75 @@
+
+/*
+ * Copyright (C) 2010 Archie L. Cobbs. All rights reserved.
+ *
+ * $Id: NotifyModemStateCommand.java 39 2011-03-22 17:21:53Z archie.cobbs $
+ */
+
+package gnu.io.rfc2217;
+
+import static gnu.io.rfc2217.RFC2217.COM_PORT_OPTION;
+import static gnu.io.rfc2217.RFC2217.NOTIFY_MODEMSTATE;
+import static gnu.io.rfc2217.RFC2217.SERVER_OFFSET;
+
+/**
+ * RFC 2217 {@code NOTIFY-MODEMSTATE} command.
+ *
+ * @see RFC 2217
+ */
+public class NotifyModemStateCommand extends ComPortCommand {
+
+ private int modemState;
+
+ /**
+ * Decoding constructor.
+ *
+ * @param bytes encoded option starting with the {@code COM-PORT-OPTION} byte
+ * @throws NullPointerException if {@code bytes} is null
+ * @throws IllegalArgumentException if {@code bytes} has length != 3
+ * @throws IllegalArgumentException if {@code bytes[0]} is not {@link RFC2217#COM_PORT_OPTION}
+ * @throws IllegalArgumentException if {@code bytes[1]} is not {@link RFC2217#NOTIFY_MODEMSTATE} (client or server)
+ */
+ public NotifyModemStateCommand(int[] bytes) {
+ super("NOTIFY-MODEMSTATE", NOTIFY_MODEMSTATE, bytes);
+ this.modemState = bytes[2];
+ }
+
+ /**
+ * Encoding constructor.
+ *
+ * @param modemState modem state value
+ * @param client true for the client-to-server command, false for the server-to-client command
+ */
+ public NotifyModemStateCommand(boolean client, int modemState) {
+ this(new int[] {
+ COM_PORT_OPTION,
+ client ? NOTIFY_MODEMSTATE : NOTIFY_MODEMSTATE + SERVER_OFFSET,
+ modemState
+ });
+ }
+
+ @Override
+ public String toString() {
+ return this.getName() + " " + Util.decodeBits(this.modemState, Util.MODEM_STATE_BITS);
+ }
+
+ @Override
+ public void visit(ComPortCommandSwitch sw) {
+ sw.caseNotifyModemState(this);
+ }
+
+ public int getModemState() {
+ return this.modemState;
+ }
+
+ @Override
+ int getMinPayloadLength() {
+ return 1;
+ }
+
+ @Override
+ int getMaxPayloadLength() {
+ return 1;
+ }
+}
+
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/ParityCommand.java b/nrjavaserial/src/main/java/gnu/io/rfc2217/ParityCommand.java
new file mode 100644
index 00000000..c611ca16
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/ParityCommand.java
@@ -0,0 +1,118 @@
+
+/*
+ * Copyright (C) 2010 Archie L. Cobbs. All rights reserved.
+ *
+ * $Id: ParityCommand.java 39 2011-03-22 17:21:53Z archie.cobbs $
+ */
+
+package gnu.io.rfc2217;
+
+import static gnu.io.rfc2217.RFC2217.COM_PORT_OPTION;
+import static gnu.io.rfc2217.RFC2217.PARITY_EVEN;
+import static gnu.io.rfc2217.RFC2217.PARITY_MARK;
+import static gnu.io.rfc2217.RFC2217.PARITY_NONE;
+import static gnu.io.rfc2217.RFC2217.PARITY_ODD;
+import static gnu.io.rfc2217.RFC2217.PARITY_REQUEST;
+import static gnu.io.rfc2217.RFC2217.PARITY_SPACE;
+import static gnu.io.rfc2217.RFC2217.SERVER_OFFSET;
+import static gnu.io.rfc2217.RFC2217.SET_PARITY;
+
+/**
+ * RFC 2217 {@code SET-PARITY} command.
+ *
+ * @see RFC 2217
+ */
+public class ParityCommand extends ComPortCommand {
+
+ private int parity;
+
+ /**
+ * Decoding constructor.
+ *
+ * @param bytes encoded option starting with the {@code COM-PORT-OPTION} byte
+ * @throws NullPointerException if {@code bytes} is null
+ * @throws IllegalArgumentException if {@code bytes} has length != 3
+ * @throws IllegalArgumentException if {@code bytes[0]} is not {@link RFC2217#COM_PORT_OPTION}
+ * @throws IllegalArgumentException if {@code bytes[1]} is not {@link RFC2217#SET_PARITY} (client or server)
+ * @throws IllegalArgumentException if {@code bytes[2]} is not a valid RFC 2217 parity value
+ */
+ public ParityCommand(int[] bytes) {
+ super("SET-PARITY", SET_PARITY, bytes);
+ this.parity = bytes[2];
+ switch (this.parity) {
+ case PARITY_REQUEST:
+ case PARITY_NONE:
+ case PARITY_ODD:
+ case PARITY_EVEN:
+ case PARITY_MARK:
+ case PARITY_SPACE:
+ break;
+ default:
+ throw new IllegalArgumentException("invalid parity value " + this.parity);
+ }
+ }
+
+ /**
+ * Encoding constructor.
+ *
+ * @param parity parity value
+ * @param client true for the client-to-server command, false for the server-to-client command
+ * @throws IllegalArgumentException if {@code parity} is not a valid RFC 2217 parity value
+ */
+ public ParityCommand(boolean client, int parity) {
+ this(new int[] {
+ COM_PORT_OPTION,
+ client ? SET_PARITY : SET_PARITY + SERVER_OFFSET,
+ parity
+ });
+ }
+
+ @Override
+ public String toString() {
+ String desc;
+ switch (this.parity) {
+ case PARITY_REQUEST:
+ desc = "REQUEST";
+ break;
+ case PARITY_NONE:
+ desc = "NONE";
+ break;
+ case PARITY_ODD:
+ desc = "ODD";
+ break;
+ case PARITY_EVEN:
+ desc = "EVEN";
+ break;
+ case PARITY_MARK:
+ desc = "MARK";
+ break;
+ case PARITY_SPACE:
+ desc = "SPACE";
+ break;
+ default:
+ desc = "?";
+ break;
+ }
+ return this.getName() + " " + desc;
+ }
+
+ @Override
+ public void visit(ComPortCommandSwitch sw) {
+ sw.caseParity(this);
+ }
+
+ public int getParity() {
+ return this.parity;
+ }
+
+ @Override
+ int getMinPayloadLength() {
+ return 1;
+ }
+
+ @Override
+ int getMaxPayloadLength() {
+ return 1;
+ }
+}
+
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/PurgeDataCommand.java b/nrjavaserial/src/main/java/gnu/io/rfc2217/PurgeDataCommand.java
new file mode 100644
index 00000000..98b85deb
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/PurgeDataCommand.java
@@ -0,0 +1,107 @@
+
+/*
+ * Copyright (C) 2010 Archie L. Cobbs. All rights reserved.
+ *
+ * $Id: PurgeDataCommand.java 39 2011-03-22 17:21:53Z archie.cobbs $
+ */
+
+package gnu.io.rfc2217;
+
+import static gnu.io.rfc2217.RFC2217.COM_PORT_OPTION;
+import static gnu.io.rfc2217.RFC2217.PURGE_DATA;
+import static gnu.io.rfc2217.RFC2217.PURGE_DATA_BOTH_DATA_BUFFERS;
+import static gnu.io.rfc2217.RFC2217.PURGE_DATA_RECEIVE_DATA_BUFFER;
+import static gnu.io.rfc2217.RFC2217.PURGE_DATA_TRANSMIT_DATA_BUFFER;
+import static gnu.io.rfc2217.RFC2217.SERVER_OFFSET;
+
+/**
+ * RFC 2217 {@code PURGE-DATA} command.
+ *
+ * @see RFC 2217
+ */
+public class PurgeDataCommand extends ComPortCommand {
+
+ private int purgeData;
+
+ /**
+ * Decoding constructor.
+ *
+ * @param bytes encoded option starting with the {@code COM-PORT-OPTION} byte
+ * @throws NullPointerException if {@code bytes} is null
+ * @throws IllegalArgumentException if {@code bytes} has length != 3
+ * @throws IllegalArgumentException if {@code bytes[0]} is not {@link RFC2217#COM_PORT_OPTION}
+ * @throws IllegalArgumentException if {@code bytes[1]} is not {@link RFC2217#PURGE_DATA} (client or server)
+ * @throws IllegalArgumentException if {@code bytes[2]} is not a valid RFC 2217 purge data value
+ */
+ public PurgeDataCommand(int[] bytes) {
+ super("PURGE-DATA", PURGE_DATA, bytes);
+ this.purgeData = bytes[2];
+ switch (this.purgeData) {
+ case PURGE_DATA_RECEIVE_DATA_BUFFER:
+ case PURGE_DATA_TRANSMIT_DATA_BUFFER:
+ case PURGE_DATA_BOTH_DATA_BUFFERS:
+ break;
+ default:
+ throw new IllegalArgumentException("invalid purge data value " + this.purgeData);
+ }
+ }
+
+ /**
+ * Encoding constructor.
+ *
+ * @param purgeData purge data value
+ * @param client true for the client-to-server command, false for the server-to-client command
+ * @throws IllegalArgumentException if {@code purgeData} is not a valid RFC 2217 purge data value
+ */
+ public PurgeDataCommand(boolean client, int purgeData) {
+ this(new int[] {
+ COM_PORT_OPTION,
+ client ? PURGE_DATA : PURGE_DATA + SERVER_OFFSET,
+ purgeData
+ });
+ }
+
+ @Override
+ public String toString() {
+ String desc;
+ switch (this.purgeData) {
+ case PURGE_DATA_RECEIVE_DATA_BUFFER:
+ desc = "RECEIVE-DATA-BUFFER";
+ break;
+ case PURGE_DATA_TRANSMIT_DATA_BUFFER:
+ desc = "TRANSMIT-DATA-BUFFER";
+ break;
+ case PURGE_DATA_BOTH_DATA_BUFFERS:
+ desc = "BOTH-DATA-BUFFERS";
+ break;
+ default:
+ desc = "?";
+ break;
+ }
+ return this.getName() + " " + desc;
+ }
+
+ @Override
+ public void visit(ComPortCommandSwitch sw) {
+ sw.casePurgeData(this);
+ }
+
+ public boolean isPurgeReceiveDataBuffer() {
+ return (this.purgeData & PURGE_DATA_RECEIVE_DATA_BUFFER) != 0;
+ }
+
+ public boolean isPurgeTransmitDataBuffer() {
+ return (this.purgeData & PURGE_DATA_TRANSMIT_DATA_BUFFER) != 0;
+ }
+
+ @Override
+ int getMinPayloadLength() {
+ return 1;
+ }
+
+ @Override
+ int getMaxPayloadLength() {
+ return 1;
+ }
+}
+
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/RFC2217.java b/nrjavaserial/src/main/java/gnu/io/rfc2217/RFC2217.java
new file mode 100644
index 00000000..d955de93
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/RFC2217.java
@@ -0,0 +1,164 @@
+
+/*
+ * Copyright (C) 2010 Archie L. Cobbs. All rights reserved.
+ *
+ * $Id: RFC2217.java 29 2010-11-22 21:57:58Z archie.cobbs $
+ */
+
+package gnu.io.rfc2217;
+
+
+
+/**
+ * RFC 2217 constants and utility methods.
+ *
+ * @see RFC 2217
+ */
+public final class RFC2217 {
+
+ // COM-PORT-OPTION telnet option
+ public static final int COM_PORT_OPTION = 44;
+
+ // COM-PORT-OPTION commands
+ public static final int SIGNATURE = 0;
+ public static final int SET_BAUDRATE = 1;
+ public static final int SET_DATASIZE = 2;
+ public static final int SET_PARITY = 3;
+ public static final int SET_STOPSIZE = 4;
+ public static final int SET_CONTROL = 5;
+ public static final int NOTIFY_LINESTATE = 6;
+ public static final int NOTIFY_MODEMSTATE = 7;
+ public static final int FLOWCONTROL_SUSPEND = 8;
+ public static final int FLOWCONTROL_RESUME = 9;
+ public static final int SET_LINESTATE_MASK = 10;
+ public static final int SET_MODEMSTATE_MASK = 11;
+ public static final int PURGE_DATA = 12;
+ public static final int SERVER_OFFSET = 100;
+
+ // SET_DATASIZE values
+ public static final int DATASIZE_REQUEST = 0;
+ public static final int DATASIZE_5 = 5;
+ public static final int DATASIZE_6 = 6;
+ public static final int DATASIZE_7 = 7;
+ public static final int DATASIZE_8 = 8;
+
+ // SET_PARITY values
+ public static final int PARITY_REQUEST = 0;
+ public static final int PARITY_NONE = 1;
+ public static final int PARITY_ODD = 2;
+ public static final int PARITY_EVEN = 3;
+ public static final int PARITY_MARK = 4;
+ public static final int PARITY_SPACE = 5;
+
+ // SET_STOPSIZE values
+ public static final int STOPSIZE_REQUEST = 0;
+ public static final int STOPSIZE_1 = 1;
+ public static final int STOPSIZE_2 = 2;
+ public static final int STOPSIZE_1_5 = 3;
+
+ // SET_CONTROL values
+ public static final int CONTROL_OUTBOUND_FLOW_REQUEST = 0;
+ public static final int CONTROL_OUTBOUND_FLOW_NONE = 1;
+ public static final int CONTROL_OUTBOUND_FLOW_XON_XOFF = 2;
+ public static final int CONTROL_OUTBOUND_FLOW_HARDWARE = 3;
+ public static final int CONTROL_BREAK_REQUEST = 4;
+ public static final int CONTROL_BREAK_ON = 5;
+ public static final int CONTROL_BREAK_OFF = 6;
+ public static final int CONTROL_DTR_REQUEST = 7;
+ public static final int CONTROL_DTR_ON = 8;
+ public static final int CONTROL_DTR_OFF = 9;
+ public static final int CONTROL_RTS_REQUEST = 10;
+ public static final int CONTROL_RTS_ON = 11;
+ public static final int CONTROL_RTS_OFF = 12;
+ public static final int CONTROL_INBOUND_FLOW_REQUEST = 13;
+ public static final int CONTROL_INBOUND_FLOW_NONE = 14;
+ public static final int CONTROL_INBOUND_FLOW_XON_XOFF = 15;
+ public static final int CONTROL_INBOUND_FLOW_HARDWARE = 16;
+ public static final int CONTROL_OUTBOUND_FLOW_DCD = 17;
+ public static final int CONTROL_INBOUND_FLOW_DTR = 18;
+ public static final int CONTROL_OUTBOUND_FLOW_DSR = 19;
+
+ // SET_LINESTATE_MASK bit values
+ public static final int LINESTATE_TIME_OUT = 0x80;
+ public static final int LINESTATE_TRANSFER_SHIFT_REGISTER_EMPTY = 0x40;
+ public static final int LINESTATE_TRANSFER_HOLDING_REGISTER_EMPTY = 0x20;
+ public static final int LINESTATE_BREAK_DETECT = 0x10;
+ public static final int LINESTATE_FRAMING_ERROR = 0x08;
+ public static final int LINESTATE_PARITY_ERROR = 0x04;
+ public static final int LINESTATE_OVERRUN_ERROR = 0x02;
+ public static final int LINESTATE_DATA_READY = 0x01;
+
+ // SET_MODEMSTATE_MASK bit values
+ public static final int MODEMSTATE_CARRIER_DETECT = 0x80;
+ public static final int MODEMSTATE_RING_INDICATOR = 0x40;
+ public static final int MODEMSTATE_DSR = 0x20;
+ public static final int MODEMSTATE_CTS = 0x10;
+ public static final int MODEMSTATE_DELTA_CARRIER_DETECT = 0x08;
+ public static final int MODEMSTATE_TRAILING_EDGE_RING_DETECTOR = 0x04;
+ public static final int MODEMSTATE_DELTA_DSR = 0x02;
+ public static final int MODEMSTATE_DELTA_CTS = 0x01;
+
+ // PURGE_DATA values
+ public static final int PURGE_DATA_RECEIVE_DATA_BUFFER = 0x01;
+ public static final int PURGE_DATA_TRANSMIT_DATA_BUFFER = 0x02;
+ public static final int PURGE_DATA_BOTH_DATA_BUFFERS = 0x03;
+
+ private RFC2217() {
+ }
+
+ /**
+ * Decode an RFC 2217 {@code COM-PORT-OPTION} command.
+ *
+ * @throws IllegalArgumentException if the bytes are not a valid encoded RFC 2217 {@link #COM_PORT_OPTION}
+ */
+ public static ComPortCommand decodeComPortCommand(int[] bytes) {
+ if (bytes.length < 2)
+ throw new IllegalArgumentException("length < 2");
+ if (bytes[0] != COM_PORT_OPTION)
+ throw new IllegalArgumentException("not a COM-PORT-OPTION (option = " + bytes[0] + ")");
+ switch (bytes[1]) {
+ case SIGNATURE:
+ case SIGNATURE + SERVER_OFFSET:
+ return new SignatureCommand(bytes);
+ case SET_BAUDRATE:
+ case SET_BAUDRATE + SERVER_OFFSET:
+ return new BaudRateCommand(bytes);
+ case SET_DATASIZE:
+ case SET_DATASIZE + SERVER_OFFSET:
+ return new DataSizeCommand(bytes);
+ case SET_PARITY:
+ case SET_PARITY + SERVER_OFFSET:
+ return new ParityCommand(bytes);
+ case SET_STOPSIZE:
+ case SET_STOPSIZE + SERVER_OFFSET:
+ return new StopSizeCommand(bytes);
+ case SET_CONTROL:
+ case SET_CONTROL + SERVER_OFFSET:
+ return new ControlCommand(bytes);
+ case NOTIFY_LINESTATE:
+ case NOTIFY_LINESTATE + SERVER_OFFSET:
+ return new NotifyLineStateCommand(bytes);
+ case NOTIFY_MODEMSTATE:
+ case NOTIFY_MODEMSTATE + SERVER_OFFSET:
+ return new NotifyModemStateCommand(bytes);
+ case FLOWCONTROL_SUSPEND:
+ case FLOWCONTROL_SUSPEND + SERVER_OFFSET:
+ return new FlowControlSuspendCommand(bytes);
+ case FLOWCONTROL_RESUME:
+ case FLOWCONTROL_RESUME + SERVER_OFFSET:
+ return new FlowControlResumeCommand(bytes);
+ case SET_LINESTATE_MASK:
+ case SET_LINESTATE_MASK + SERVER_OFFSET:
+ return new LineStateMaskCommand(bytes);
+ case SET_MODEMSTATE_MASK:
+ case SET_MODEMSTATE_MASK + SERVER_OFFSET:
+ return new ModemStateMaskCommand(bytes);
+ case PURGE_DATA:
+ case PURGE_DATA + SERVER_OFFSET:
+ return new PurgeDataCommand(bytes);
+ default:
+ throw new IllegalArgumentException("unrecognized COM-PORT-OPTION command " + bytes[1]);
+ }
+ }
+}
+
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/SignatureCommand.java b/nrjavaserial/src/main/java/gnu/io/rfc2217/SignatureCommand.java
new file mode 100644
index 00000000..56f97ba9
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/SignatureCommand.java
@@ -0,0 +1,113 @@
+
+/*
+ * Copyright (C) 2010 Archie L. Cobbs. All rights reserved.
+ *
+ * $Id: SignatureCommand.java 39 2011-03-22 17:21:53Z archie.cobbs $
+ */
+
+package gnu.io.rfc2217;
+
+import java.io.UnsupportedEncodingException;
+
+import static gnu.io.rfc2217.RFC2217.COM_PORT_OPTION;
+import static gnu.io.rfc2217.RFC2217.SERVER_OFFSET;
+import static gnu.io.rfc2217.RFC2217.SIGNATURE;
+
+/**
+ * RFC 2217 {@code SIGNATURE} command.
+ *
+ * @see RFC 2217
+ */
+public class SignatureCommand extends ComPortCommand {
+
+ public static final String ENCODING = "ISO-8859-1";
+
+ private final String signature;
+ private boolean decodeFailed;
+
+ /**
+ * Decoding constructor.
+ *
+ * @param bytes encoded option starting with the {@code COM-PORT-OPTION} byte
+ * @throws NullPointerException if {@code bytes} is null
+ * @throws IllegalArgumentException if {@code bytes} has length that is too short or too long
+ * @throws IllegalArgumentException if {@code bytes[0]} is not {@link RFC2217#COM_PORT_OPTION}
+ * @throws IllegalArgumentException if {@code bytes[1]} is not {@link RFC2217#SIGNATURE} (client or server)
+ */
+ public SignatureCommand(int[] bytes) {
+ super("SIGNATURE", SIGNATURE, bytes);
+ String sig;
+ boolean failed = false;
+ try {
+ sig = new String(this.getPayload(), ENCODING);
+ } catch (UnsupportedEncodingException e) {
+ sig = "(string decode failed)";
+ failed = true;
+ }
+ this.signature = sig;
+ this.decodeFailed = failed;
+ }
+
+ /**
+ * Encoding constructor.
+ *
+ * @param signature signature string
+ * @param client true for the client-to-server command, false for the server-to-client command
+ */
+ public SignatureCommand(boolean client, String signature) {
+ this(encode(client, signature));
+ }
+
+ /**
+ * Encoding constructor for signature requests.
+ *
+ * @param client true for the client command, false for the server command
+ */
+ public SignatureCommand(boolean client) {
+ this(encode(client, ""));
+ }
+
+ @Override
+ public String toString() {
+ return this.getName() + " " + (this.signature.length() > 0 ? "\"" + this.signature + "\"" : "REQUEST");
+ }
+
+ @Override
+ public void visit(ComPortCommandSwitch sw) {
+ sw.caseSignature(this);
+ }
+
+ public String getSignature() {
+ return this.signature;
+ }
+
+ public boolean decodeFailed() {
+ return this.decodeFailed;
+ }
+
+ @Override
+ int getMinPayloadLength() {
+ return 0;
+ }
+
+ @Override
+ int getMaxPayloadLength() {
+ return Integer.MAX_VALUE - 2;
+ }
+
+ private static int[] encode(boolean client, String signature) {
+ byte[] buf;
+ try {
+ buf = signature.getBytes(ENCODING);
+ } catch (UnsupportedEncodingException e) {
+ buf = new byte[] { (byte)'?' };
+ }
+ int[] ibuf = new int[2 + buf.length];
+ ibuf[0] = COM_PORT_OPTION;
+ ibuf[1] = client ? SIGNATURE : SIGNATURE + SERVER_OFFSET;
+ for (int i = 0; i < buf.length; i++)
+ ibuf[2 + i] = buf[i] & 0xff;
+ return ibuf;
+ }
+}
+
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/StopSizeCommand.java b/nrjavaserial/src/main/java/gnu/io/rfc2217/StopSizeCommand.java
new file mode 100644
index 00000000..08b99ead
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/StopSizeCommand.java
@@ -0,0 +1,108 @@
+
+/*
+ * Copyright (C) 2010 Archie L. Cobbs. All rights reserved.
+ *
+ * $Id: StopSizeCommand.java 39 2011-03-22 17:21:53Z archie.cobbs $
+ */
+
+package gnu.io.rfc2217;
+
+import static gnu.io.rfc2217.RFC2217.COM_PORT_OPTION;
+import static gnu.io.rfc2217.RFC2217.SERVER_OFFSET;
+import static gnu.io.rfc2217.RFC2217.SET_STOPSIZE;
+import static gnu.io.rfc2217.RFC2217.STOPSIZE_1;
+import static gnu.io.rfc2217.RFC2217.STOPSIZE_1_5;
+import static gnu.io.rfc2217.RFC2217.STOPSIZE_2;
+import static gnu.io.rfc2217.RFC2217.STOPSIZE_REQUEST;
+
+/**
+ * RFC 2217 {@code SET-STOPSIZE} command.
+ *
+ * @see RFC 2217
+ */
+public class StopSizeCommand extends ComPortCommand {
+
+ private int stopSize;
+
+ /**
+ * Decoding constructor.
+ *
+ * @param bytes encoded option starting with the {@code COM-PORT-OPTION} byte
+ * @throws NullPointerException if {@code bytes} is null
+ * @throws IllegalArgumentException if {@code bytes} has length != 3
+ * @throws IllegalArgumentException if {@code bytes[0]} is not {@link RFC2217#COM_PORT_OPTION}
+ * @throws IllegalArgumentException if {@code bytes[1]} is not {@link RFC2217#SET_STOPSIZE} (client or server)
+ * @throws IllegalArgumentException if {@code bytes[2]} is not a valid RFC 2217 stop size value
+ */
+ public StopSizeCommand(int[] bytes) {
+ super("SET-STOPSIZE", SET_STOPSIZE, bytes);
+ this.stopSize = bytes[2];
+ switch (this.stopSize) {
+ case STOPSIZE_REQUEST:
+ case STOPSIZE_1:
+ case STOPSIZE_2:
+ case STOPSIZE_1_5:
+ break;
+ default:
+ throw new IllegalArgumentException("invalid stop size value " + this.stopSize);
+ }
+ }
+
+ /**
+ * Encoding constructor.
+ *
+ * @param stopSize stop size value
+ * @param client true for the client-to-server command, false for the server-to-client command
+ * @throws IllegalArgumentException if {@code stopSize} is not a valid RFC 2217 stop size value
+ */
+ public StopSizeCommand(boolean client, int stopSize) {
+ this(new int[] {
+ COM_PORT_OPTION,
+ client ? SET_STOPSIZE : SET_STOPSIZE + SERVER_OFFSET,
+ stopSize
+ });
+ }
+
+ @Override
+ public String toString() {
+ String desc;
+ switch (this.stopSize) {
+ case STOPSIZE_REQUEST:
+ desc = "REQUEST";
+ break;
+ case STOPSIZE_1:
+ desc = "1";
+ break;
+ case STOPSIZE_2:
+ desc = "2";
+ break;
+ case STOPSIZE_1_5:
+ desc = "1.5";
+ break;
+ default:
+ desc = "?";
+ break;
+ }
+ return this.getName() + " " + desc;
+ }
+
+ @Override
+ public void visit(ComPortCommandSwitch sw) {
+ sw.caseStopSize(this);
+ }
+
+ public int getStopSize() {
+ return this.stopSize;
+ }
+
+ @Override
+ int getMinPayloadLength() {
+ return 1;
+ }
+
+ @Override
+ int getMaxPayloadLength() {
+ return 1;
+ }
+}
+
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/TelnetSerialPort.java b/nrjavaserial/src/main/java/gnu/io/rfc2217/TelnetSerialPort.java
new file mode 100644
index 00000000..402b4490
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/TelnetSerialPort.java
@@ -0,0 +1,1088 @@
+
+/*
+ * Copyright (C) 2010 Archie L. Cobbs. All rights reserved.
+ *
+ * $Id: TelnetSerialPort.java 48 2012-03-18 20:54:33Z archie.cobbs $
+ */
+
+package gnu.io.rfc2217;
+
+import gnu.io.SerialPort;
+import gnu.io.SerialPortEvent;
+import gnu.io.SerialPortEventListener;
+import gnu.io.UnsupportedCommOperationException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.TooManyListenersException;
+
+import org.apache.commons.net.telnet.EchoOptionHandler;
+import org.apache.commons.net.telnet.InvalidTelnetOptionException;
+import org.apache.commons.net.telnet.SuppressGAOptionHandler;
+import org.apache.commons.net.telnet.TelnetClient;
+import org.apache.commons.net.telnet.TelnetInputListener;
+import org.apache.commons.net.telnet.TerminalTypeOptionHandler;
+
+import static gnu.io.rfc2217.RFC2217.CONTROL_BREAK_OFF;
+import static gnu.io.rfc2217.RFC2217.CONTROL_BREAK_ON;
+import static gnu.io.rfc2217.RFC2217.CONTROL_DTR_OFF;
+import static gnu.io.rfc2217.RFC2217.CONTROL_DTR_ON;
+import static gnu.io.rfc2217.RFC2217.CONTROL_INBOUND_FLOW_HARDWARE;
+import static gnu.io.rfc2217.RFC2217.CONTROL_INBOUND_FLOW_NONE;
+import static gnu.io.rfc2217.RFC2217.CONTROL_INBOUND_FLOW_XON_XOFF;
+import static gnu.io.rfc2217.RFC2217.CONTROL_OUTBOUND_FLOW_HARDWARE;
+import static gnu.io.rfc2217.RFC2217.CONTROL_OUTBOUND_FLOW_NONE;
+import static gnu.io.rfc2217.RFC2217.CONTROL_OUTBOUND_FLOW_XON_XOFF;
+import static gnu.io.rfc2217.RFC2217.CONTROL_RTS_OFF;
+import static gnu.io.rfc2217.RFC2217.CONTROL_RTS_ON;
+import static gnu.io.rfc2217.RFC2217.DATASIZE_5;
+import static gnu.io.rfc2217.RFC2217.DATASIZE_6;
+import static gnu.io.rfc2217.RFC2217.DATASIZE_7;
+import static gnu.io.rfc2217.RFC2217.DATASIZE_8;
+import static gnu.io.rfc2217.RFC2217.LINESTATE_BREAK_DETECT;
+import static gnu.io.rfc2217.RFC2217.LINESTATE_DATA_READY;
+import static gnu.io.rfc2217.RFC2217.LINESTATE_FRAMING_ERROR;
+import static gnu.io.rfc2217.RFC2217.LINESTATE_OVERRUN_ERROR;
+import static gnu.io.rfc2217.RFC2217.LINESTATE_PARITY_ERROR;
+import static gnu.io.rfc2217.RFC2217.LINESTATE_TRANSFER_SHIFT_REGISTER_EMPTY;
+import static gnu.io.rfc2217.RFC2217.MODEMSTATE_CARRIER_DETECT;
+import static gnu.io.rfc2217.RFC2217.MODEMSTATE_CTS;
+import static gnu.io.rfc2217.RFC2217.MODEMSTATE_DSR;
+import static gnu.io.rfc2217.RFC2217.MODEMSTATE_RING_INDICATOR;
+import static gnu.io.rfc2217.RFC2217.STOPSIZE_1;
+import static gnu.io.rfc2217.RFC2217.STOPSIZE_1_5;
+import static gnu.io.rfc2217.RFC2217.STOPSIZE_2;
+
+/**
+ * Implements the client side of the RFC 2217
+ * serial-over-Telnet protocol as as {@link SerialPort}.
+ *
+ *
+ * This class extends the {@link SerialPort} class and functions in the same way, however, there are
+ * a couple of differences to be aware of:
+ *
+ * -
+ * To "open" a serial port, create an instance of this class, configure it as required,
+ * and then get the {@link TelnetClient} via {@link #getTelnetClient} and invoke
+ * {@link TelnetClient#connect(java.net.InetAddress, int) TelnetClient.connect()} (or one of its variants).
+ * This will create the telnet connection to the access server.
+ *
+ *
+ * -
+ * Once connected, if the underlying telnet connection is broken, an {@link IOException} will be
+ * thrown when attempting to access the serial port input or output streams. In addition, a
+ * {@link SerialPortEvent#DATA_AVAILABLE DATA_AVAILABLE} event will be immediately generated.
+ *
+ *
+ *
+ *
+ *
+ * The following optional functionality is not implemented and/or inappropriate for a networked connection:
+ *
+ * - Receive threshold
+ * - Receive timeout
+ * - Receive framing byte
+ * - Input buffer size
+ * - Output buffer size
+ *
+ *
+ *
+ *
+ * In addition, access servers typically don't support {@link #notifyOnOutputEmpty}.
+ *
+ *
+ *
+ * Finally, {@link #sendBreak} is supported but the {@code millis} argument is ignored, as timing cannot be
+ * assured over a TCP connection. Access servers typically enforce a fixed break time.
+ *
+ *
+ * @see SerialPort
+ * @see RFC 2217
+ */
+public class TelnetSerialPort extends SerialPort {
+
+ private static final int DEFAULT_BAUD_RATE = 9600;
+
+ private static final String DEFAULT_TERMINAL_TYPE = "VT100";
+
+ // Modem state bits we always want the server to report to us regardless of what listener wants.
+ // This is so we can always stay up-to-date with their values in case isCD(), etc. is invoked.
+ private static final int MODEMSTATE_ALWAYS_MONITOR
+ = MODEMSTATE_CARRIER_DETECT | MODEMSTATE_RING_INDICATOR | MODEMSTATE_DSR | MODEMSTATE_CTS;
+
+ // Line state bits we never want the server to report to us regardless of what listener wants; internally,
+ // we use the LINESTATE_DATA_READY bit only to indicate the listener wants DATA_AVAILABLE notifications.
+ private static final int LINESTATE_NEVER_MONITOR = LINESTATE_DATA_READY;
+
+ // States
+ private enum State {
+ INITIAL(false, false),
+ ESTABLISHED(true, false),
+ CLOSED(false, true);
+
+ private final boolean established;
+ private final boolean closed;
+
+ private State(boolean established, boolean closed) {
+ this.established = established;
+ this.closed = closed;
+ }
+
+ public void checkNotClosed() {
+ if (this.closed)
+ throw new IllegalStateException("port is closed");
+ }
+
+ public boolean isEstablished() {
+ return this.established;
+ }
+ }
+
+ private final TelnetClient telnetClient;
+
+ private String name;
+ private String signature;
+ private State state;
+ private SerialPortEventListener listener;
+
+ private int baudRate = DEFAULT_BAUD_RATE;
+ private int dataSize = DATASIZE_8;
+ private int flowControlInbound = CONTROL_INBOUND_FLOW_NONE;
+ private int flowControlOutbound = CONTROL_OUTBOUND_FLOW_NONE;
+ private int parity = RFC2217.PARITY_NONE;
+ private int stopSize = STOPSIZE_1;
+
+ private boolean cd;
+ private boolean cts;
+ private boolean dsr;
+ private boolean dtr;
+ private boolean ri;
+ private boolean rts;
+
+ private int lineStateNotify; // which line state changes we notify listener about
+ private int lineStateMask; // which line state changes access server notifies us about
+ private int lineStateLast; // most recent line state rec'd from access server
+ private int modemStateNotify; // which modem state changes we notify listener about
+ private int modemStateMask = MODEMSTATE_ALWAYS_MONITOR; // which modem state changes access server notifies us about
+ private int modemStateLast; // most recent modem state rec'd from access server
+
+ /**
+ * Constructor.
+ */
+ public TelnetSerialPort() {
+ this.state = State.INITIAL;
+ this.name = getClass().getSimpleName();
+ this.signature = "jvser v" + Version.JVSER_VERSION;
+ this.telnetClient = this.createTelnetClient();
+ this.telnetClient.registerInputListener(new TelnetInputListener() {
+
+ @Override
+ public void telnetInputAvailable() {
+ boolean notify;
+ synchronized (TelnetSerialPort.this) {
+ notify = (TelnetSerialPort.this.lineStateNotify & LINESTATE_DATA_READY) != 0;
+ }
+ if (notify)
+ TelnetSerialPort.this.sendEvent(SerialPortEvent.DATA_AVAILABLE);
+ }
+ });
+ }
+
+ /**
+ * Get the descriptive name of this client (used for logging purposes).
+ */
+ @Override
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * Set the descriptive name of this client (used for logging purposes).
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Get the signature sent to the remote server at connection time.
+ * By default, the signature is the name of this class.
+ */
+ public String getSignature() {
+ return this.signature;
+ }
+
+ /**
+ * Set the signature sent to the remote server at connection time.
+ *
+ * @param signature signature string, or {@code null} (or empty string) to not send a signature
+ */
+ public void setSignature(String signature) {
+ this.signature = signature;
+ }
+
+ /**
+ * Get the {@link TelnetClient} associated with this instance.
+ */
+ public TelnetClient getTelnetClient() {
+ return this.telnetClient;
+ }
+
+ /**
+ * Construct and configure the {@link TelnetClient} to be used for this instance.
+ */
+ protected TelnetClient createTelnetClient() {
+ TelnetClient tc = new TelnetClient(DEFAULT_TERMINAL_TYPE);
+ tc.setReaderThread(true); // allows immediate option negotiation
+ try {
+ tc.addOptionHandler(new TerminalTypeOptionHandler(DEFAULT_TERMINAL_TYPE, false, false, true, false));
+ tc.addOptionHandler(new EchoOptionHandler(false, false, false, false));
+ tc.addOptionHandler(new SuppressGAOptionHandler(true, true, true, true));
+ tc.addOptionHandler(new TransmitBinaryOptionHandler(true, true, true, true));
+ tc.addOptionHandler(new ComPortOptionHandler(this));
+ } catch (IOException e) {
+ throw new RuntimeException("unexpected exception", e);
+ } catch (InvalidTelnetOptionException e) {
+ throw new RuntimeException("unexpected exception", e);
+ }
+ return tc;
+ }
+
+ // We wrap the telnet port's InputStream in a NotifyInputStream so we can detect when there
+ // is new data available to be read. It would be nice if the TelnetClient provided a way to
+ // notify us directly, but it doesn't, so we have to use this hack.
+ @Override
+ public synchronized InputStream getInputStream() throws IOException {
+ this.state.checkNotClosed();
+ return this.telnetClient.getInputStream();
+ }
+
+ @Override
+ public synchronized OutputStream getOutputStream() throws IOException {
+ this.state.checkNotClosed();
+ return this.telnetClient.getOutputStream();
+ }
+
+ @Override
+ public synchronized void close() {
+ if (this.state == State.CLOSED)
+ return;
+ this.state = State.CLOSED;
+ try {
+ this.telnetClient.disconnect();
+ } catch (IOException e) {
+ //
+ }
+ }
+
+ @Override
+ public synchronized int getBaudRate() {
+ this.state.checkNotClosed();
+ return this.baudRate;
+ }
+
+ @Override
+ public synchronized int getDataBits() {
+ this.state.checkNotClosed();
+ switch (this.dataSize) {
+ case DATASIZE_5:
+ return DATABITS_5;
+ case DATASIZE_6:
+ return DATABITS_6;
+ case DATASIZE_7:
+ return DATABITS_7;
+ case DATASIZE_8:
+ return DATABITS_8;
+ default:
+ throw new RuntimeException("impossible case");
+ }
+ }
+
+ @Override
+ public synchronized int getStopBits() {
+ this.state.checkNotClosed();
+ switch (this.stopSize) {
+ case STOPSIZE_1:
+ return STOPBITS_1;
+ case STOPSIZE_2:
+ return STOPBITS_2;
+ case STOPSIZE_1_5:
+ return STOPBITS_1_5;
+ default:
+ throw new RuntimeException("impossible case");
+ }
+ }
+
+ @Override
+ public synchronized int getParity() {
+ this.state.checkNotClosed();
+ switch (this.parity) {
+ case RFC2217.PARITY_NONE:
+ return SerialPort.PARITY_NONE;
+ case RFC2217.PARITY_ODD:
+ return SerialPort.PARITY_ODD;
+ case RFC2217.PARITY_EVEN:
+ return SerialPort.PARITY_EVEN;
+ case RFC2217.PARITY_MARK:
+ return SerialPort.PARITY_MARK;
+ case RFC2217.PARITY_SPACE:
+ return SerialPort.PARITY_SPACE;
+ default:
+ throw new RuntimeException("impossible case");
+ }
+ }
+
+ @Override
+ public void sendBreak(int millis) {
+ CommandList commandList = new CommandList(2);
+ synchronized (this) {
+ this.state.checkNotClosed();
+ if (this.state != State.ESTABLISHED)
+ return;
+ commandList.add(new ControlCommand(true, CONTROL_BREAK_ON));
+ commandList.add(new ControlCommand(true, CONTROL_BREAK_OFF));
+ }
+ commandList.send();
+ }
+
+ @Override
+ public void setFlowControlMode(int flowControl) throws UnsupportedCommOperationException {
+
+ // Validate bit combination
+ if ((flowControl & (FLOWCONTROL_RTSCTS_OUT | FLOWCONTROL_XONXOFF_OUT)) == (FLOWCONTROL_RTSCTS_OUT | FLOWCONTROL_XONXOFF_OUT)
+ || (flowControl & (FLOWCONTROL_RTSCTS_IN | FLOWCONTROL_XONXOFF_IN)) == (FLOWCONTROL_RTSCTS_IN | FLOWCONTROL_XONXOFF_IN))
+ throw new UnsupportedCommOperationException("invalid flow control value " + flowControl);
+
+ // Apply changes
+ CommandList commandList = new CommandList(2);
+ synchronized (this) {
+ this.state.checkNotClosed();
+
+ // Convert to RFC 2217 values
+ int previousFlowControlOutbound = this.flowControlOutbound;
+ int previousFlowControlInbound = this.flowControlInbound;
+ this.flowControlOutbound = (flowControl & FLOWCONTROL_RTSCTS_OUT) != 0 ? CONTROL_OUTBOUND_FLOW_HARDWARE :
+ (flowControl & FLOWCONTROL_XONXOFF_OUT) != 0 ? CONTROL_OUTBOUND_FLOW_XON_XOFF : CONTROL_OUTBOUND_FLOW_NONE;
+ this.flowControlInbound = (flowControl & FLOWCONTROL_RTSCTS_IN) != 0 ? CONTROL_INBOUND_FLOW_HARDWARE :
+ (flowControl & FLOWCONTROL_XONXOFF_IN) != 0 ? CONTROL_INBOUND_FLOW_XON_XOFF : CONTROL_INBOUND_FLOW_NONE;
+
+ // Update server (outbound first per RFC 2217)
+ if (this.flowControlOutbound != previousFlowControlOutbound && this.state.isEstablished())
+ commandList.add(new ControlCommand(true, this.flowControlOutbound));
+ if (this.flowControlInbound != previousFlowControlInbound && this.state.isEstablished())
+ commandList.add(new ControlCommand(true, this.flowControlInbound));
+ }
+ commandList.send();
+ }
+
+ @Override
+ public synchronized int getFlowControlMode() {
+ this.state.checkNotClosed();
+ int value = FLOWCONTROL_NONE;
+ switch (this.flowControlOutbound) {
+ case CONTROL_OUTBOUND_FLOW_HARDWARE:
+ value |= FLOWCONTROL_RTSCTS_OUT;
+ break;
+ case CONTROL_OUTBOUND_FLOW_XON_XOFF:
+ value |= FLOWCONTROL_XONXOFF_OUT;
+ break;
+ default:
+ break;
+ }
+ switch (this.flowControlInbound) {
+ case CONTROL_INBOUND_FLOW_HARDWARE:
+ value |= FLOWCONTROL_RTSCTS_IN;
+ break;
+ case CONTROL_INBOUND_FLOW_XON_XOFF:
+ value |= FLOWCONTROL_XONXOFF_IN;
+ break;
+ default:
+ break;
+ }
+ return value;
+ }
+
+ @Override
+ public void setSerialPortParams(int baudRate, int dataBits, int stopBits, int parity)
+ throws UnsupportedCommOperationException {
+ CommandList commandList = new CommandList(4);
+ synchronized (this) {
+ this.state.checkNotClosed();
+
+ // Validate parameters and convert to RFC 2217 values
+ if (baudRate <= 0)
+ throw new UnsupportedCommOperationException("invalid baud rate " + baudRate);
+ switch (dataBits) {
+ case DATABITS_5:
+ dataBits = DATASIZE_5;
+ break;
+ case DATABITS_6:
+ dataBits = DATASIZE_6;
+ break;
+ case DATABITS_7:
+ dataBits = DATASIZE_7;
+ break;
+ case DATABITS_8:
+ dataBits = DATASIZE_8;
+ break;
+ default:
+ throw new UnsupportedCommOperationException("invalid data bits " + dataBits);
+ }
+ switch (stopBits) {
+ case STOPBITS_1:
+ stopBits = STOPSIZE_1;
+ break;
+ case STOPBITS_2:
+ stopBits = STOPSIZE_2;
+ break;
+ case STOPBITS_1_5:
+ stopBits = STOPSIZE_1_5;
+ break;
+ default:
+ throw new UnsupportedCommOperationException("invalid stop bits " + stopBits);
+ }
+ switch (parity) {
+ case SerialPort.PARITY_NONE:
+ parity = RFC2217.PARITY_NONE;
+ break;
+ case SerialPort.PARITY_ODD:
+ parity = RFC2217.PARITY_ODD;
+ break;
+ case SerialPort.PARITY_EVEN:
+ parity = RFC2217.PARITY_EVEN;
+ break;
+ case SerialPort.PARITY_MARK:
+ parity = RFC2217.PARITY_MARK;
+ break;
+ case SerialPort.PARITY_SPACE:
+ parity = RFC2217.PARITY_SPACE;
+ break;
+ default:
+ throw new UnsupportedCommOperationException("invalid parity " + parity);
+ }
+
+ // Update my state
+ boolean changed = false;
+ if (this.baudRate != baudRate) {
+ this.baudRate = baudRate;
+ changed = true;
+ }
+ if (this.dataSize != dataBits) {
+ this.dataSize = dataBits;
+ changed = true;
+ }
+ if (this.stopSize != stopBits) {
+ this.stopSize = stopBits;
+ changed = true;
+ }
+ if (this.parity != parity) {
+ this.parity = parity;
+ changed = true;
+ }
+
+ // Update access server if there was a change
+ if (changed && this.state.isEstablished())
+ this.addSerialPortGeometry(commandList);
+ }
+ commandList.send();
+ }
+
+ @Override
+ public void setDTR(boolean value) {
+ CommandList commandList = new CommandList(1);
+ synchronized (this) {
+ this.state.checkNotClosed();
+ if (this.dtr != value) {
+ this.dtr = value;
+ if (this.state.isEstablished())
+ commandList.add(new ControlCommand(true, this.dtr ? CONTROL_DTR_ON : CONTROL_DTR_OFF));
+ }
+ }
+ commandList.send();
+ }
+
+ @Override
+ public synchronized boolean isDTR() {
+ this.state.checkNotClosed();
+ return this.dtr;
+ }
+
+ @Override
+ public void setRTS(boolean value) {
+ CommandList commandList = new CommandList(1);
+ synchronized (this) {
+ this.state.checkNotClosed();
+ if (this.rts != value) {
+ this.rts = value;
+ if (this.state.isEstablished())
+ commandList.add(new ControlCommand(true, this.rts ? CONTROL_RTS_ON : CONTROL_RTS_OFF));
+ }
+ }
+ commandList.send();
+ }
+
+ @Override
+ public synchronized boolean isRTS() {
+ this.state.checkNotClosed();
+ return this.rts;
+ }
+
+ @Override
+ public synchronized boolean isCTS() {
+ this.state.checkNotClosed();
+ return this.cts;
+ }
+
+ @Override
+ public synchronized boolean isDSR() {
+ this.state.checkNotClosed();
+ return this.dsr;
+ }
+
+ @Override
+ public synchronized boolean isRI() {
+ this.state.checkNotClosed();
+ return this.ri;
+ }
+
+ @Override
+ public synchronized boolean isCD() {
+ this.state.checkNotClosed();
+ return this.cd;
+ }
+
+ // This is invoked by the ComPortOptionHandler once the server has agreed to accept COM-PORT-OPTION subnegotiation commands
+
+ void startSubnegotiation() {
+ CommandList commandList = new CommandList(12);
+ synchronized (this) {
+
+ // Update state
+ this.state.checkNotClosed();
+ this.state = State.ESTABLISHED;
+
+ // Request signature from peer
+ commandList.add(new SignatureCommand(true));
+
+ // Send signature if desired
+ if (this.signature != null && this.signature.length() > 0)
+ commandList.add(new SignatureCommand(true, this.signature));
+
+ // Send all configuration information
+ this.addSerialPortGeometry(commandList);
+ commandList.add(new LineStateMaskCommand(true, this.lineStateMask));
+ commandList.add(new ModemStateMaskCommand(true, this.modemStateMask));
+ commandList.add(new ControlCommand(true, this.flowControlInbound));
+ commandList.add(new ControlCommand(true, this.flowControlOutbound));
+ commandList.add(new ControlCommand(true, this.dtr ? CONTROL_DTR_ON : CONTROL_DTR_OFF));
+ commandList.add(new ControlCommand(true, this.rts ? CONTROL_RTS_ON : CONTROL_RTS_OFF));
+ }
+ commandList.send();
+ }
+
+ // Method to send serial port "geometry" in the order recommended by RFC 2217 (section 2)
+
+ private void addSerialPortGeometry(CommandList commandList) {
+ commandList.add(new BaudRateCommand(true, this.baudRate));
+ commandList.add(new DataSizeCommand(true, this.dataSize));
+ commandList.add(new ParityCommand(true, this.parity));
+ commandList.add(new StopSizeCommand(true, this.stopSize));
+ }
+
+ // This is invoked by the ComPortOptionHandler when we receive a command from the server
+
+ void handleCommand(ComPortCommand command) {
+
+ // Incoming commands should be server versions
+ if (!command.isServerCommand()) {
+ return;
+ }
+
+ // Handle command
+ command.visit(new AbstractComPortCommandSwitch() {
+
+ @Override
+ public void caseBaudRate(BaudRateCommand command) {
+ synchronized (TelnetSerialPort.this) {
+ TelnetSerialPort.this.baudRate = command.getBaudRate();
+ }
+ }
+
+ @Override
+ public void caseDataSize(DataSizeCommand command) {
+ synchronized (TelnetSerialPort.this) {
+ TelnetSerialPort.this.dataSize = command.getDataSize();
+ }
+ }
+
+ @Override
+ public void caseParity(ParityCommand command) {
+ synchronized (TelnetSerialPort.this) {
+ TelnetSerialPort.this.parity = command.getParity();
+ }
+ }
+
+ @Override
+ public void caseStopSize(StopSizeCommand command) {
+ synchronized (TelnetSerialPort.this) {
+ TelnetSerialPort.this.stopSize = command.getStopSize();
+ }
+ }
+
+ @Override
+ public void caseControl(ControlCommand command) {
+ synchronized (TelnetSerialPort.this) {
+ switch (command.getControl()) {
+ case CONTROL_OUTBOUND_FLOW_NONE:
+ case CONTROL_OUTBOUND_FLOW_XON_XOFF:
+ case CONTROL_OUTBOUND_FLOW_HARDWARE:
+ TelnetSerialPort.this.flowControlOutbound = command.getControl();
+ break;
+ case CONTROL_INBOUND_FLOW_NONE:
+ case CONTROL_INBOUND_FLOW_XON_XOFF:
+ case CONTROL_INBOUND_FLOW_HARDWARE:
+ TelnetSerialPort.this.flowControlInbound = command.getControl();
+ break;
+ case CONTROL_DTR_ON:
+ TelnetSerialPort.this.dtr = true;
+ break;
+ case CONTROL_DTR_OFF:
+ TelnetSerialPort.this.dtr = false;
+ break;
+ case CONTROL_RTS_ON:
+ TelnetSerialPort.this.rts = true;
+ break;
+ case CONTROL_RTS_OFF:
+ TelnetSerialPort.this.rts = false;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void caseNotifyLineState(NotifyLineStateCommand command) {
+ int lineState = command.getLineState();
+ int notify;
+ synchronized (TelnetSerialPort.this) {
+ notify = TelnetSerialPort.this.lineStateNotify;
+ TelnetSerialPort.this.lineStateLast = lineState;
+ }
+ notify &= lineState; // notify only if bit is equal to 1
+ if ((notify & LINESTATE_TRANSFER_SHIFT_REGISTER_EMPTY) != 0)
+ TelnetSerialPort.this.sendEvent(SerialPortEvent.OUTPUT_BUFFER_EMPTY);
+ if ((notify & LINESTATE_BREAK_DETECT) != 0)
+ TelnetSerialPort.this.sendEvent(SerialPortEvent.BI);
+ if ((notify & LINESTATE_FRAMING_ERROR) != 0)
+ TelnetSerialPort.this.sendEvent(SerialPortEvent.FE);
+ if ((notify & LINESTATE_PARITY_ERROR) != 0)
+ TelnetSerialPort.this.sendEvent(SerialPortEvent.PE);
+ if ((notify & LINESTATE_OVERRUN_ERROR) != 0)
+ TelnetSerialPort.this.sendEvent(SerialPortEvent.OE);
+ }
+
+ @Override
+ public void caseNotifyModemState(NotifyModemStateCommand command) {
+ int modemState = command.getModemState();
+ int notify;
+ synchronized (TelnetSerialPort.this) {
+ notify = TelnetSerialPort.this.modemStateNotify;
+ TelnetSerialPort.this.modemStateLast = modemState;
+ }
+ notify &= modemState ^ modemStateLast; // notify only if bit has changed
+ if ((notify & MODEMSTATE_CARRIER_DETECT) != 0)
+ TelnetSerialPort.this.sendEvent(SerialPortEvent.CD, (modemState & MODEMSTATE_CARRIER_DETECT) != 0);
+ if ((notify & MODEMSTATE_RING_INDICATOR) != 0)
+ TelnetSerialPort.this.sendEvent(SerialPortEvent.RI, (modemState & MODEMSTATE_RING_INDICATOR) != 0);
+ if ((notify & MODEMSTATE_DSR) != 0)
+ TelnetSerialPort.this.sendEvent(SerialPortEvent.DSR, (modemState & MODEMSTATE_DSR) != 0);
+ if ((notify & MODEMSTATE_CTS) != 0)
+ TelnetSerialPort.this.sendEvent(SerialPortEvent.CTS, (modemState & MODEMSTATE_CTS) != 0);
+ }
+
+ @Override
+ protected void caseDefault(ComPortCommand command) {
+ //
+ }
+ });
+ }
+
+ // Listener management
+
+ @Override
+ public synchronized void addEventListener(SerialPortEventListener listener) throws TooManyListenersException {
+ this.state.checkNotClosed();
+ if (this.listener != null)
+ throw new TooManyListenersException("only one listener allowed");
+ this.listener = listener;
+ }
+
+ @Override
+ public synchronized void removeEventListener() {
+ this.listener = null;
+ }
+
+ // Notification configuration
+
+ @Override
+ public synchronized void notifyOnDataAvailable(boolean value) {
+ this.state.checkNotClosed();
+ updateLineStateMask(LINESTATE_DATA_READY, value);
+ }
+
+ @Override
+ public void notifyOnOutputEmpty(boolean value) {
+ CommandList commandList = new CommandList(1);
+ synchronized (this) {
+ this.state.checkNotClosed();
+ if (this.updateLineStateMask(LINESTATE_TRANSFER_SHIFT_REGISTER_EMPTY, value) && this.state.isEstablished())
+ commandList.add(new LineStateMaskCommand(true, this.lineStateMask));
+ }
+ commandList.send();
+ }
+
+ @Override
+ public void notifyOnCTS(boolean value) {
+ CommandList commandList = new CommandList(1);
+ synchronized (this) {
+ this.state.checkNotClosed();
+ if (updateModemStateMask(MODEMSTATE_CTS, value) && this.state.isEstablished())
+ commandList.add(new ModemStateMaskCommand(true, this.modemStateMask));
+ }
+ commandList.send();
+ }
+
+ @Override
+ public void notifyOnDSR(boolean value) {
+ CommandList commandList = new CommandList(1);
+ synchronized (this) {
+ this.state.checkNotClosed();
+ if (updateModemStateMask(MODEMSTATE_DSR, value) && this.state.isEstablished())
+ commandList.add(new ModemStateMaskCommand(true, this.modemStateMask));
+ }
+ commandList.send();
+ }
+
+ @Override
+ public void notifyOnRingIndicator(boolean value) {
+ CommandList commandList = new CommandList(1);
+ synchronized (this) {
+ this.state.checkNotClosed();
+ if (updateModemStateMask(MODEMSTATE_RING_INDICATOR, value) && this.state.isEstablished())
+ commandList.add(new ModemStateMaskCommand(true, this.modemStateMask));
+ }
+ commandList.send();
+ }
+
+ @Override
+ public void notifyOnCarrierDetect(boolean value) {
+ CommandList commandList = new CommandList(1);
+ synchronized (this) {
+ this.state.checkNotClosed();
+ if (updateModemStateMask(MODEMSTATE_CARRIER_DETECT, value) && this.state.isEstablished())
+ commandList.add(new ModemStateMaskCommand(true, this.modemStateMask));
+ }
+ commandList.send();
+ }
+
+ @Override
+ public void notifyOnOverrunError(boolean value) {
+ CommandList commandList = new CommandList(1);
+ synchronized (this) {
+ this.state.checkNotClosed();
+ if (this.updateLineStateMask(LINESTATE_OVERRUN_ERROR, value) && this.state.isEstablished())
+ commandList.add(new LineStateMaskCommand(true, this.lineStateMask));
+ }
+ commandList.send();
+ }
+
+ @Override
+ public void notifyOnParityError(boolean value) {
+ CommandList commandList = new CommandList(1);
+ synchronized (this) {
+ this.state.checkNotClosed();
+ if (this.updateLineStateMask(LINESTATE_PARITY_ERROR, value) && this.state.isEstablished())
+ commandList.add(new LineStateMaskCommand(true, this.lineStateMask));
+ }
+ commandList.send();
+ }
+
+ @Override
+ public void notifyOnFramingError(boolean value) {
+ CommandList commandList = new CommandList(1);
+ synchronized (this) {
+ this.state.checkNotClosed();
+ if (this.updateLineStateMask(LINESTATE_FRAMING_ERROR, value) && this.state.isEstablished())
+ commandList.add(new LineStateMaskCommand(true, this.lineStateMask));
+ }
+ commandList.send();
+ }
+
+ @Override
+ public void notifyOnBreakInterrupt(boolean value) {
+ CommandList commandList = new CommandList(1);
+ synchronized (this) {
+ this.state.checkNotClosed();
+ if (this.updateLineStateMask(LINESTATE_BREAK_DETECT, value) && this.state.isEstablished())
+ commandList.add(new LineStateMaskCommand(true, this.lineStateMask));
+ }
+ commandList.send();
+ }
+
+ // Methods for sending event notifications
+
+ private void sendEvent(int type) {
+ this.sendEvent(type, true);
+ }
+
+ private void sendEvent(int type, boolean newValue) {
+ SerialPortEventListener currentListener;
+ synchronized (this) {
+ currentListener = this.listener;
+ }
+ if (currentListener == null)
+ return;
+ SerialPortEvent event = new SerialPortEvent(this, type, !newValue, newValue);
+ try {
+ currentListener.serialEvent(event);
+ } catch (Exception e) {
+ System.err.println(this.name + ": exception from listener " + listener + ": " + e.getMessage());
+ }
+ }
+
+ // Internal utility methods
+
+ // Send a subnegotiation to the peer
+ private void sendSubnegotiation(ComPortCommand command) {
+ assert !Thread.holdsLock(TelnetSerialPort.this); // otherwise we can deadlock
+ try {
+ this.telnetClient.sendSubnegotiation(command.getBytes());
+ } catch (IOException e) {
+ System.err.println(this.name + ": exception sending subcommand: " + e.getMessage());
+ }
+ }
+
+ // Update line state notifications; return true if we need to send new mask to access server
+ private synchronized boolean updateLineStateMask(int bit, boolean value) {
+ int previous = this.lineStateMask;
+ if (value) {
+ this.lineStateNotify |= bit;
+ this.lineStateMask |= bit;
+ } else {
+ this.lineStateNotify &= ~bit;
+ this.lineStateMask &= ~bit;
+ }
+ this.lineStateMask &= ~LINESTATE_NEVER_MONITOR;
+ return this.lineStateMask != previous;
+ }
+
+ // Update modem state notifications; return true if we need to send new mask to access server
+ private synchronized boolean updateModemStateMask(int bit, boolean value) {
+ int previous = this.modemStateMask;
+ if (value) {
+ this.modemStateNotify |= bit;
+ this.modemStateMask |= bit;
+ } else {
+ this.modemStateNotify &= ~bit;
+ this.modemStateMask &= ~bit;
+ }
+ this.modemStateMask |= MODEMSTATE_ALWAYS_MONITOR;
+ return this.modemStateMask != previous;
+ }
+
+ // Unimplemented methods
+
+ @Override
+ public synchronized void enableReceiveThreshold(int threshold) throws UnsupportedCommOperationException {
+ this.state.checkNotClosed();
+ throw new UnsupportedCommOperationException();
+ }
+
+ @Override
+ public synchronized void disableReceiveThreshold() {
+ this.state.checkNotClosed();
+ }
+
+ @Override
+ public synchronized boolean isReceiveThresholdEnabled() {
+ this.state.checkNotClosed();
+ return false;
+ }
+
+ @Override
+ public synchronized int getReceiveThreshold() {
+ this.state.checkNotClosed();
+ return 0;
+ }
+
+ @Override
+ public synchronized void enableReceiveTimeout(int timeout) throws UnsupportedCommOperationException {
+ this.state.checkNotClosed();
+ throw new UnsupportedCommOperationException();
+ }
+
+ @Override
+ public synchronized void disableReceiveTimeout() {
+ this.state.checkNotClosed();
+ }
+
+ @Override
+ public synchronized boolean isReceiveTimeoutEnabled() {
+ this.state.checkNotClosed();
+ return false;
+ }
+
+ @Override
+ public synchronized int getReceiveTimeout() {
+ this.state.checkNotClosed();
+ return 0;
+ }
+
+ @Override
+ public synchronized void enableReceiveFraming(int framingByte) throws UnsupportedCommOperationException {
+ this.state.checkNotClosed();
+ throw new UnsupportedCommOperationException();
+ }
+
+ @Override
+ public synchronized void disableReceiveFraming() {
+ this.state.checkNotClosed();
+ }
+
+ @Override
+ public synchronized boolean isReceiveFramingEnabled() {
+ this.state.checkNotClosed();
+ return false;
+ }
+
+ @Override
+ public synchronized int getReceiveFramingByte() {
+ this.state.checkNotClosed();
+ return 0;
+ }
+
+ @Override
+ public synchronized void setInputBufferSize(int size) {
+ this.state.checkNotClosed();
+ }
+
+ @Override
+ public synchronized int getInputBufferSize() {
+ this.state.checkNotClosed();
+ return 0;
+ }
+
+ @Override
+ public synchronized void setOutputBufferSize(int size) {
+ this.state.checkNotClosed();
+ }
+
+ @Override
+ public synchronized int getOutputBufferSize() {
+ this.state.checkNotClosed();
+ return 0;
+ }
+
+ // Utility class
+
+ @SuppressWarnings("serial")
+ private class CommandList extends ArrayList {
+
+ public CommandList(int size) {
+ super(size);
+ }
+
+ public void send() {
+ for (ComPortCommand command : this)
+ TelnetSerialPort.this.sendSubnegotiation(command);
+ this.clear();
+ }
+ }
+
+ @Override
+ public int getBaudBase() throws UnsupportedCommOperationException, IOException {
+ this.state.checkNotClosed();
+ return baudRate;
+ }
+
+ @Override
+ public boolean getCallOutHangup() throws UnsupportedCommOperationException {
+ this.state.checkNotClosed();
+ throw new UnsupportedCommOperationException();
+ }
+
+ @Override
+ public int getDivisor() throws UnsupportedCommOperationException, IOException {
+ this.state.checkNotClosed();
+ return 1;
+ }
+
+ @Override
+ public byte getEndOfInputChar() throws UnsupportedCommOperationException {
+ this.state.checkNotClosed();
+ return 0x04;
+ }
+
+ @Override
+ public boolean getLowLatency() throws UnsupportedCommOperationException {
+ this.state.checkNotClosed();
+ throw new UnsupportedCommOperationException();
+ }
+
+ @Override
+ public byte getParityErrorChar() throws UnsupportedCommOperationException {
+ this.state.checkNotClosed();
+ throw new UnsupportedCommOperationException();
+ }
+
+ @Override
+ public String getUARTType() throws UnsupportedCommOperationException {
+ this.state.checkNotClosed();
+ throw new UnsupportedCommOperationException();
+ }
+
+ @Override
+ public boolean setBaudBase(int arg0) throws UnsupportedCommOperationException, IOException {
+ this.state.checkNotClosed();
+ return true;
+ }
+
+ @Override
+ public boolean setCallOutHangup(boolean arg0) throws UnsupportedCommOperationException {
+ this.state.checkNotClosed();
+ throw new UnsupportedCommOperationException();
+ }
+
+ @Override
+ public boolean setDivisor(int arg0) throws UnsupportedCommOperationException, IOException {
+ this.state.checkNotClosed();
+ return true;
+ }
+
+ @Override
+ public boolean setEndOfInputChar(byte arg0) throws UnsupportedCommOperationException {
+ this.state.checkNotClosed();
+ return true;
+ }
+
+ @Override
+ public boolean setLowLatency() throws UnsupportedCommOperationException {
+ this.state.checkNotClosed();
+ throw new UnsupportedCommOperationException();
+ }
+
+ @Override
+ public boolean setParityErrorChar(byte arg0) throws UnsupportedCommOperationException {
+ this.state.checkNotClosed();
+ throw new UnsupportedCommOperationException();
+ }
+
+ @Override
+ public boolean setUARTType(String arg0, boolean arg1) throws UnsupportedCommOperationException {
+ this.state.checkNotClosed();
+ throw new UnsupportedCommOperationException();
+ }
+}
+
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/TransmitBinaryOptionHandler.java b/nrjavaserial/src/main/java/gnu/io/rfc2217/TransmitBinaryOptionHandler.java
new file mode 100644
index 00000000..a02f513c
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/TransmitBinaryOptionHandler.java
@@ -0,0 +1,40 @@
+
+/*
+ * Copyright (C) 2010 Archie L. Cobbs. All rights reserved.
+ *
+ * $Id: TransmitBinaryOptionHandler.java 46 2011-10-16 23:06:39Z archie.cobbs $
+ */
+
+package gnu.io.rfc2217;
+
+import org.apache.commons.net.telnet.TelnetOptionHandler;
+
+/**
+ * Handler for the telnet {@code TRANSMIT-BINARY} option defined by RFC 856.
+ *
+ * @see RFC 856
+ */
+public class TransmitBinaryOptionHandler extends TelnetOptionHandler {
+
+ public static final int TRANSMIT_BINARY_OPTION = 0;
+
+ public TransmitBinaryOptionHandler(boolean initlocal, boolean initremote, boolean acceptlocal, boolean acceptremote) {
+ super(TRANSMIT_BINARY_OPTION, initlocal, initremote, acceptlocal, acceptremote);
+ }
+
+ @Override
+ public int[] answerSubnegotiation(int[] data, int length) {
+ return null;
+ }
+
+ @Override
+ public int[] startSubnegotiationLocal() {
+ return null;
+ }
+
+ @Override
+ public int[] startSubnegotiationRemote() {
+ return null;
+ }
+}
+
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/Util.java b/nrjavaserial/src/main/java/gnu/io/rfc2217/Util.java
new file mode 100644
index 00000000..8f95d333
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/Util.java
@@ -0,0 +1,70 @@
+
+/*
+ * Copyright (C) 2010 Archie L. Cobbs. All rights reserved.
+ *
+ * $Id: Util.java 6 2010-11-20 23:37:06Z archie.cobbs $
+ */
+
+package gnu.io.rfc2217;
+
+import java.util.ArrayList;
+
+/**
+ * Utility methods.
+ */
+final class Util {
+
+ static final String[] LINE_STATE_BITS = new String[] {
+ "TIME_OUT",
+ "TRANSFER_SHIFT_REGISTER_EMPTY",
+ "TRANSFER_HOLDING_REGISTER_EMPTY",
+ "BREAK_DETECT",
+ "FRAMING_ERROR",
+ "PARITY_ERROR",
+ "OVERRUN_ERROR",
+ "DATA_READY",
+ };
+
+ static final String[] MODEM_STATE_BITS = new String[] {
+ "CARRIER_DETECT",
+ "RING_INDICATOR",
+ "DSR",
+ "CTS",
+ "DELTA_CARRIER_DETECT",
+ "TRAILING_EDGE_RING_DETECTOR",
+ "DELTA_DSR",
+ "DELTA_CTS",
+ };
+
+ private Util() {
+ }
+
+ static String decodeBits(int value, String[] names) {
+ ArrayList list = new ArrayList(8);
+ for (int i = 0; i < 8; i++) {
+ if ((value & (1 << (7 - i))) != 0)
+ list.add(names[i]);
+ }
+ if (list.isEmpty())
+ return "(none)";
+ names = list.toArray(new String[list.size()]);
+ StringBuilder buf = new StringBuilder();
+ for (int i = 0; i < names.length; i++) {
+ if (i > 0)
+ buf.append(' ');
+ buf.append(names[i]);
+ }
+ return buf.toString();
+ }
+
+ static String rawBytes(int[] data, int off, int len) {
+ StringBuilder buf = new StringBuilder();
+ for (int i = 0; i < len; i++) {
+ if (i > 0)
+ buf.append(' ');
+ buf.append(String.format("0x%02x", data[off + i]));
+ }
+ return buf.toString();
+ }
+}
+
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/Version.java b/nrjavaserial/src/main/java/gnu/io/rfc2217/Version.java
new file mode 100644
index 00000000..3e6c7cd0
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/Version.java
@@ -0,0 +1,48 @@
+
+/*
+ * Copyright (C) 2010 Archie L. Cobbs. All rights reserved.
+ *
+ * $Id$
+ */
+
+package gnu.io.rfc2217;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+/**
+ * Contains library version information.
+ */
+public final class Version {
+
+ /**
+ * The version of this library.
+ */
+ public static final String JVSER_VERSION;
+
+ private static final String PROPERTIES_RESOURCE = "/jvser.properties";
+ private static final String VERSION_PROPERTY_NAME = "jvser.version";
+
+ static {
+ Properties properties = new Properties();
+ InputStream input = Version.class.getResourceAsStream(PROPERTIES_RESOURCE);
+ if (input == null)
+ throw new RuntimeException("can't find resource " + PROPERTIES_RESOURCE);
+ try {
+ properties.load(input);
+ } catch (IOException e) {
+ throw new RuntimeException("unexpected exception", e);
+ } finally {
+ try {
+ input.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ JVSER_VERSION = properties.getProperty(VERSION_PROPERTY_NAME, "?");
+ }
+
+ private Version() {
+ }
+}
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/overview.html b/nrjavaserial/src/main/java/gnu/io/rfc2217/overview.html
new file mode 100644
index 00000000..82c42082
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/overview.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+This is the API documentation for version @VERSION@ of the jvser
+library, which implements the client side of the RFC 2217
+serial-over-Telnet protocol.
+
+
+
+RFC 2217 defines a TCP protcol for virtualizing
+traditional serial ports over a telnet connection. This allows clients to connect to remote serial
+lines using access servers that support this protocol.
+
+
+
+This library provides the TelnetSerialPort
class
+for RFC 2217 serial port connections.
+This class implements the
+javax.comm.SerialPort
+API, and so is backward-compatible with existing Java software that works with serial ports.
+
+
+
+Visit the jvser homepage at http://jvser.googlecode.com/
.
+
+
+
diff --git a/nrjavaserial/src/main/java/gnu/io/rfc2217/package-info.java b/nrjavaserial/src/main/java/gnu/io/rfc2217/package-info.java
new file mode 100644
index 00000000..62bcd6e3
--- /dev/null
+++ b/nrjavaserial/src/main/java/gnu/io/rfc2217/package-info.java
@@ -0,0 +1,19 @@
+
+/*
+ * Copyright (C) 2010 Archie L. Cobbs. All rights reserved.
+ *
+ * $Id: package-info.java 31 2010-11-22 22:43:26Z archie.cobbs $
+ */
+
+/**
+ * Top level Java package for the jvser RFC 2217 client library.
+ *
+ *
+ * The central class in this package is {@link gnu.io.rfc2217.TelnetSerialPort}.
+ *
+ *
+ * @see gnu.io.rfc2217.TelnetSerialPort
+ * @see RFC 2217
+ * @see The jvser project home page
+ */
+package gnu.io.rfc2217;
diff --git a/nrjavaserial/src/main/resources/jvser.properties b/nrjavaserial/src/main/resources/jvser.properties
new file mode 100644
index 00000000..73508fca
--- /dev/null
+++ b/nrjavaserial/src/main/resources/jvser.properties
@@ -0,0 +1,3 @@
+# $Id: jvser.properties 13 2010-11-22 15:02:45Z archie.cobbs $
+
+jvser.version=1.0.48