Skip to content

Commit

Permalink
Add Serial Port Support (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
firejox authored Apr 16, 2023
1 parent a7bdfd8 commit 5a9798a
Show file tree
Hide file tree
Showing 6 changed files with 271 additions and 2 deletions.
27 changes: 25 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
78 changes: 78 additions & 0 deletions Tests/SerialPortPiperInfoTest.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
1 change: 1 addition & 0 deletions Tests/Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<PackageReference Include="NUnit.Analyzers" Version="3.3.0" />
<PackageReference Include="coverlet.collector" Version="3.1.2" />
<PackageReference Include="System.IO.Pipelines" Version="6.0.3" />
<PackageReference Include="System.IO.Ports" Version="7.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
6 changes: 6 additions & 0 deletions winsocat/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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!;
}

Expand Down Expand Up @@ -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!;
}

Expand Down
160 changes: 160 additions & 0 deletions winsocat/Serial.cs
Original file line number Diff line number Diff line change
@@ -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!;
}
}
1 change: 1 addition & 0 deletions winsocat/winsocat.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

<None Include="..\README.md" Pack="true" PackagePath="\" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
<PackageReference Include="System.IO.Ports" Version="7.0.0" />
</ItemGroup>

</Project>

0 comments on commit 5a9798a

Please sign in to comment.