From 5a9798a71f1a2c356747e265b39e53461dccc6b3 Mon Sep 17 00:00:00 2001 From: Firejox Date: Sun, 16 Apr 2023 23:45:50 +0800 Subject: [PATCH] Add Serial Port Support (#3) --- README.md | 27 +++++- Tests/SerialPortPiperInfoTest.cs | 78 +++++++++++++++ Tests/Tests.csproj | 1 + winsocat/Program.cs | 6 ++ winsocat/Serial.cs | 160 +++++++++++++++++++++++++++++++ winsocat/winsocat.csproj | 1 + 6 files changed, 271 insertions(+), 2 deletions(-) create mode 100644 Tests/SerialPortPiperInfoTest.cs create mode 100644 winsocat/Serial.cs diff --git a/README.md b/README.md index 15afc09..5df26da 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,9 @@ The WinSocat is accept two address pattern winsocat.exe [address1] [address2] ``` -The `address1` can accept `STDIO`, `TCP-LISTEN`, `TCP`, `NPIPE`, `NPIPE-LISTEN`, `EXEC`, `WSL`, `UNIX`, `UNIX-LISTEN`, `HVSOCK`, `HVSOCK-LISTEN` socket types. +The `address1` can accept `STDIO`, `TCP-LISTEN`, `TCP`, `NPIPE`, `NPIPE-LISTEN`, `EXEC`, `WSL`, `UNIX`, `UNIX-LISTEN`, `HVSOCK`, `HVSOCK-LISTEN`, `SP` socket types. -The `address2` can accept `STDIO`, `TCP`, `NPIPE`, `EXEC`, `WSL`, `UNIX`, `HVSOCK` socket types. +The `address2` can accept `STDIO`, `TCP`, `NPIPE`, `EXEC`, `WSL`, `UNIX`, `HVSOCK`, `SP` socket types. ## Examples @@ -98,3 +98,26 @@ winsocat stdio hvsock:0cb41c0b-fd26-4a41-8370-dccb048e216e:vsock-2761 ``` This `vsock-2761` will be viewed as the serviceId `00000ac9-facb-11e6-bd58-64006a7986d3`. + +### Serial Port Support + +WinSocat can relay the data of serial port. For example, + +``` +winsocat sp:COM1,baudrate=12500,parity=1,databits=16,stopbits=0 stdio +``` + +The `baudrate`, `parity`, `databits` and `stopbits` is optional parameter. Another example is to integrate +with [com0com](https://sourceforge.net/projects/com0com/). + +1. Assume you have already created paired com port `COM5 <=> COM6` via [com0com](https://sourceforge.net/projects/com0com/). +2. Execute the command at terminal +``` +winsocat sp:COM5 stdio +``` +3. Execute the command at another terminal +``` +winsocat sp:COM6 stdio +``` + +Now these two terminals can interact with each other. \ No newline at end of file diff --git a/Tests/SerialPortPiperInfoTest.cs b/Tests/SerialPortPiperInfoTest.cs new file mode 100644 index 0000000..77e8764 --- /dev/null +++ b/Tests/SerialPortPiperInfoTest.cs @@ -0,0 +1,78 @@ +using System.IO.Ports; +using Firejox.App.WinSocat; + +namespace APPTest; + +public class SerialPortPiperInfoTest +{ + + [TestCase("SP:COM1")] + [TestCase("SP:COM2,baudrate=12500,parity=1,databits=16,stopbits=0")] + public void ValidInputParseTest(string input) + { + var element = AddressElement.TryParse(input); + Assert.NotNull(SerialPortPiperInfo.TryParse(element)); + } + + [TestCase("sp:COM1")] + [TestCase("sp:COM2,baudrate=12500,parity=1,databits=16,stopbits=0")] + public void CaseInsensitiveValidInputParseTest(string input) + { + var element = AddressElement.TryParse(input); + Assert.NotNull(SerialPortPiperInfo.TryParse(element)); + } + + [TestCase("STDIO")] + [TestCase("TCP:127.0.0.1:80")] + [TestCase("TCP-LISTEN:127.0.0.1:80")] + [TestCase("NPIPE:fooServer:barPipe")] + [TestCase("NPIPE-LISTEN:fooPipe")] + [TestCase(@"EXEC:'C:\Foo.exe bar'")] + [TestCase("UNIX:foo.sock")] + [TestCase("UNIX-LISTEN:foo.sock")] + public void InvalidInputParseTest(string input) + { + var element = AddressElement.TryParse(input); + Assert.Null(SerialPortPiperInfo.TryParse(element)); + } + + [TestCase("sp:COM1", ExpectedResult = "COM1")] + [TestCase("sp:COM2,baudrate=12500,parity=1,databits=16,stopbits=0", ExpectedResult = "COM2")] + public string PortNamePatternParseTest(string input) + { + var element = AddressElement.TryParse(input); + return SerialPortPiperInfo.TryParse(element).PortName; + } + + [TestCase("sp:COM1", ExpectedResult = 9600)] + [TestCase("sp:COM2,baudrate=12500,parity=1,databits=16,stopbits=0", ExpectedResult = 12500)] + public int BaudRatePatternParseTest(string input) + { + var element = AddressElement.TryParse(input); + return SerialPortPiperInfo.TryParse(element).BaudRate; + } + + [TestCase("sp:COM1", ExpectedResult = Parity.None)] + [TestCase("sp:COM2,baudrate=12500,parity=1,databits=16,stopbits=0", ExpectedResult = Parity.Odd)] + public Parity PartityPatternParseTest(string input) + { + var element = AddressElement.TryParse(input); + return SerialPortPiperInfo.TryParse(element).Partiy; + } + + [TestCase("sp:COM1", ExpectedResult = 8)] + [TestCase("sp:COM2,baudrate=12500,parity=1,databits=16,stopbits=0", ExpectedResult = 16)] + public int DataBitsPatternParseTest(string input) + { + var element = AddressElement.TryParse(input); + return SerialPortPiperInfo.TryParse(element).DataBits; + } + + [TestCase("sp:COM1", ExpectedResult = StopBits.One)] + [TestCase("sp:COM2,baudrate=12500,parity=1,databits=16,stopbits=0", ExpectedResult = StopBits.None)] + public StopBits StopBitsPatternParseTest(string input) + { + var element = AddressElement.TryParse(input); + return SerialPortPiperInfo.TryParse(element).StopBits; + } +} \ No newline at end of file diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 0f5b43d..44c45b5 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -17,6 +17,7 @@ + diff --git a/winsocat/Program.cs b/winsocat/Program.cs index 5b18713..f0778c9 100644 --- a/winsocat/Program.cs +++ b/winsocat/Program.cs @@ -73,6 +73,9 @@ private static IPiperStrategy PiperStrategyParse(string input) if ((strategy = HyperVListenPiperStrategy.TryParse(element)) != null) return strategy; + if ((strategy = SerialPortPiperStrategy.TryParse(element)) != null) + return strategy; + return strategy!; } @@ -106,6 +109,9 @@ private static IPiperFactory PiperFactoryParse(string input) if ((factory = HyperVStreamPiperFactory.TryParse(element)) != null) return factory; + if ((factory = SerialPortPiperFactory.TryParse(element)) != null) + return factory; + return factory!; } diff --git a/winsocat/Serial.cs b/winsocat/Serial.cs new file mode 100644 index 0000000..445c276 --- /dev/null +++ b/winsocat/Serial.cs @@ -0,0 +1,160 @@ +using System.IO.Ports; + +namespace Firejox.App.WinSocat; + +public class SerialPortPiperInfo +{ + private readonly string _portName; + public string PortName => _portName; + + private readonly int _baudRate; + public int BaudRate => _baudRate; + + private readonly Parity _parity; + public Parity Partiy => _parity; + + private readonly int _dataBits; + public int DataBits => _dataBits; + + private readonly StopBits _stopBits; + public StopBits StopBits => _stopBits; + + public SerialPortPiperInfo(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits) + { + _portName = portName; + _baudRate = baudRate; + _parity = parity; + _dataBits = dataBits; + _stopBits = stopBits; + } + + public static SerialPortPiperInfo TryParse(AddressElement element) + { + if (!element.Tag.Equals("SP", StringComparison.OrdinalIgnoreCase)) + return null!; + + string portName = element.Address; + int baudRate; + Parity parity; + int dataBits; + StopBits stopBits; + + if (!element.Options.TryGetValue("baudrate", out var tmp)) + baudRate = 9600; + else if (!Int32.TryParse(tmp, out baudRate)) + return null!; + + if (!element.Options.TryGetValue("parity", out tmp!)) + parity = Parity.None; + else if (!Enum.TryParse(tmp, out parity)) + return null!; + + if (!element.Options.TryGetValue("databits", out tmp!)) + dataBits = 8; + else if (!Int32.TryParse(tmp, out dataBits)) + return null!; + + if (!element.Options.TryGetValue("stopbits", out tmp!)) + stopBits = StopBits.One; + else if (!Enum.TryParse(tmp, out stopBits)) + return null!; + return new SerialPortPiperInfo(portName, baudRate, parity, dataBits, stopBits); + } +} + +public class SerialPortPiper : StreamPiper +{ + private SerialPort _serialPort; + + public SerialPortPiper(SerialPort serialPort) : base(OpenAndGet(serialPort)) + { + _serialPort = serialPort; + } + + protected override void Dispose(bool disposing) + { + try + { + base.Dispose(disposing); + if (disposing && _serialPort is not null) + { + _serialPort.Dispose(); + } + } + finally + { + _serialPort = null!; + } + } + + private static Stream OpenAndGet(SerialPort serialPort) + { + if (!serialPort.IsOpen) + serialPort.Open(); + return serialPort.BaseStream; + } +} + +public class SerialPortPiperFactory : IPiperFactory +{ + private readonly SerialPortPiperInfo _info; + + public SerialPortPiperFactory(SerialPortPiperInfo info) + { + _info = info; + } + + public IPiper NewPiper() + { + return new SerialPortPiper( + new SerialPort( + _info.PortName, + _info.BaudRate, + _info.Partiy, + _info.DataBits, + _info.StopBits + ) + ); + } + + public static SerialPortPiperFactory TryParse(AddressElement element) + { + SerialPortPiperInfo info; + if ((info = SerialPortPiperInfo.TryParse(element)) is not null) + return new SerialPortPiperFactory(info); + return null!; + } +} + +public class SerialPortPiperStrategy : PiperStrategy +{ + private readonly SerialPortPiperInfo _info; + + public SerialPortPiperStrategy(SerialPortPiperInfo info) + { + _info = info; + } + + protected override IPiper NewPiper() + { + return new SerialPortPiper( + new SerialPort( + _info.PortName, + _info.BaudRate, + _info.Partiy, + _info.DataBits, + _info.StopBits + ) + ); + } + + public static SerialPortPiperStrategy TryParse(AddressElement element) + { + SerialPortPiperInfo info; + + if ((info = SerialPortPiperInfo.TryParse(element)) is not null) + return new SerialPortPiperStrategy(info); + + return null!; + } +} \ No newline at end of file diff --git a/winsocat/winsocat.csproj b/winsocat/winsocat.csproj index ce5b449..fdc1716 100644 --- a/winsocat/winsocat.csproj +++ b/winsocat/winsocat.csproj @@ -31,6 +31,7 @@ +