mirror of
https://github.com/ekinnee/SharpCAT.git
synced 2026-01-11 02:50:03 +01:00
Implement SharpCAT Common library with configuration, logging, certificates, and CAT abstractions
Co-authored-by: ekinnee <1707617+ekinnee@users.noreply.github.com>
This commit is contained in:
parent
ba3966d448
commit
7916fc3ba5
41
SharpCAT.sln
Normal file
41
SharpCAT.sln
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4E2188E0-16ED-42CA-9BF8-E38C5E682404}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpCAT.Common", "src\SharpCAT.Common\SharpCAT.Common.csproj", "{28874A1C-98B0-43D6-BE83-6E3AD8B9F42F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpCAT.Server", "src\SharpCAT.Server\SharpCAT.Server.csproj", "{EF0F9B07-4F63-4057-ACE7-663BF43B88DC}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpCAT.Client", "src\SharpCAT.Client\SharpCAT.Client.csproj", "{3CA1501E-A86D-436A-9A18-8A3A2952098C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{28874A1C-98B0-43D6-BE83-6E3AD8B9F42F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{28874A1C-98B0-43D6-BE83-6E3AD8B9F42F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{28874A1C-98B0-43D6-BE83-6E3AD8B9F42F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{28874A1C-98B0-43D6-BE83-6E3AD8B9F42F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EF0F9B07-4F63-4057-ACE7-663BF43B88DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EF0F9B07-4F63-4057-ACE7-663BF43B88DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EF0F9B07-4F63-4057-ACE7-663BF43B88DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EF0F9B07-4F63-4057-ACE7-663BF43B88DC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3CA1501E-A86D-436A-9A18-8A3A2952098C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3CA1501E-A86D-436A-9A18-8A3A2952098C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3CA1501E-A86D-436A-9A18-8A3A2952098C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3CA1501E-A86D-436A-9A18-8A3A2952098C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{28874A1C-98B0-43D6-BE83-6E3AD8B9F42F} = {4E2188E0-16ED-42CA-9BF8-E38C5E682404}
|
||||
{EF0F9B07-4F63-4057-ACE7-663BF43B88DC} = {4E2188E0-16ED-42CA-9BF8-E38C5E682404}
|
||||
{3CA1501E-A86D-436A-9A18-8A3A2952098C} = {4E2188E0-16ED-42CA-9BF8-E38C5E682404}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -1,10 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SharpCATLib
|
||||
{
|
||||
class FlrigProtocol
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
namespace SharpCATLib.Models
|
||||
{
|
||||
internal class CATCommand
|
||||
{
|
||||
public string P1 { get; set; }
|
||||
public string P2 { get; set; }
|
||||
public string P3 { get; set; }
|
||||
public string P4 { get; set; }
|
||||
public string OpCode { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
namespace SharpCATLib.Models
|
||||
{
|
||||
//Base Radio Model
|
||||
public partial class CATRadio : IRadio
|
||||
{
|
||||
private string RadioMfg { get; }
|
||||
private string RadioModel { get; }
|
||||
|
||||
private string CmdPad { get; } = "00000000";
|
||||
|
||||
private string VFOToggle { get; }
|
||||
private Lock Lock { get; }
|
||||
private Ptt Ptt { get; }
|
||||
private Clar Clar { get; }
|
||||
private Split Split { get; }
|
||||
private Power Power { get; }
|
||||
private ToneMode ToneMode { get; }
|
||||
private OpModes OpModes { get; }
|
||||
|
||||
partial void LockOn();
|
||||
|
||||
partial void LockOff();
|
||||
|
||||
partial void PttOn();
|
||||
|
||||
partial void PttOff();
|
||||
|
||||
partial void ClarOn();
|
||||
|
||||
partial void ClarOff();
|
||||
|
||||
partial void SplitOn();
|
||||
|
||||
partial void SplitOff();
|
||||
|
||||
partial void PowerOn();
|
||||
|
||||
partial void PowerOff();
|
||||
|
||||
partial void SetFreq(double freq);
|
||||
|
||||
partial void SetOpMode(OpModes opmode);
|
||||
|
||||
partial void SwitchVFO();
|
||||
|
||||
partial void SetToneMode(ToneMode mode);
|
||||
|
||||
partial void GetRXStatus();
|
||||
|
||||
partial void GetTXStatus();
|
||||
|
||||
partial void GetFreqAndModeStatus();
|
||||
}
|
||||
|
||||
public partial class Lock
|
||||
{
|
||||
public static readonly string ON;
|
||||
public static readonly string OFF;
|
||||
}
|
||||
|
||||
public class Ptt
|
||||
{
|
||||
public static readonly string ON;
|
||||
public static readonly string OFF;
|
||||
}
|
||||
|
||||
public class Clar
|
||||
{
|
||||
public static readonly string ON;
|
||||
public static readonly string OFF;
|
||||
}
|
||||
|
||||
public class Split
|
||||
{
|
||||
public static readonly string ON;
|
||||
public static readonly string OFF;
|
||||
}
|
||||
|
||||
public class Power
|
||||
{
|
||||
public static readonly string ON;
|
||||
public static readonly string OFF;
|
||||
}
|
||||
|
||||
public class ToneMode
|
||||
{
|
||||
public static readonly string DCS;
|
||||
public static readonly string CTCSS;
|
||||
public static readonly string ENCODER;
|
||||
public static readonly string OFF;
|
||||
}
|
||||
|
||||
public class OpModes
|
||||
{
|
||||
public static readonly string LSB;
|
||||
public static readonly string USB;
|
||||
public static readonly string CW;
|
||||
public static readonly string CWR;
|
||||
public static readonly string AM;
|
||||
public static readonly string FM;
|
||||
public static readonly string DIG;
|
||||
public static readonly string PKT;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
namespace SharpCATLib.Models
|
||||
{
|
||||
internal class CIVCommand
|
||||
{
|
||||
private string CmdToRadio = "FE FE 9A E0 Cn Sc Data area FD";
|
||||
private string DataFromRadio = "FE FE E0 9A Cn Sc Data area FD";
|
||||
private string OKFromRadio { get; set; }
|
||||
private string NGFromRadio { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
namespace SharpCATLib.Models
|
||||
{
|
||||
internal class CIVRadio : IRadio
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
using System;
|
||||
namespace SharpCATLib.Models
|
||||
{
|
||||
partial interface IRadio
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
using SharpCATLib.Models;
|
||||
|
||||
namespace SharpCATLib.Radios.Icom
|
||||
{
|
||||
internal class ID4100a : CIVRadio
|
||||
{
|
||||
private string OKFromRadio = "FEFEE09AFBFD";
|
||||
private string NGFromRadio = "FEFEE09AFAFD";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
{
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
namespace SharpCATLib.Radios.Icom
|
||||
{
|
||||
internal class ID880H
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
{
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
namespace SharpCATLib.Radios.Kenwood
|
||||
{
|
||||
internal class THD74A
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
{
|
||||
}
|
||||
|
|
@ -1,149 +0,0 @@
|
|||
using SharpCATLib.Models;
|
||||
|
||||
namespace SharpCATLib.Radios.Yaesu
|
||||
{
|
||||
public class FT818 : CATRadio
|
||||
{
|
||||
public string RadioMfg => "Yaesu";
|
||||
public string RadioModel => "FT-818";
|
||||
|
||||
public string CmdPad => "00000000";
|
||||
|
||||
public class Lock
|
||||
{
|
||||
public static readonly string ON = "00";
|
||||
public static readonly string OFF = "80";
|
||||
}
|
||||
|
||||
public class Ptt
|
||||
{
|
||||
public static readonly string ON = "08";
|
||||
public static readonly string OFF = "88";
|
||||
}
|
||||
|
||||
public class Clar
|
||||
{
|
||||
public static readonly string ON = "05";
|
||||
public static readonly string OFF = "85";
|
||||
}
|
||||
|
||||
public class Split
|
||||
{
|
||||
public static readonly string ON = "02";
|
||||
public static readonly string OFF = "82";
|
||||
}
|
||||
|
||||
public class Power
|
||||
{
|
||||
public static readonly string ON = "0F";
|
||||
public static readonly string OFF = "8F";
|
||||
}
|
||||
|
||||
public string VFOToggle => "81";
|
||||
|
||||
public class ToneMode
|
||||
{
|
||||
public static readonly string DCS = "0A";
|
||||
public static readonly string CTCSS = "2A";
|
||||
public static readonly string ENCODER = "4A";
|
||||
public static readonly string OFF = "8A";
|
||||
}
|
||||
|
||||
public class OpModes
|
||||
{
|
||||
public static readonly string LSB = "00";
|
||||
public static readonly string USB = "01";
|
||||
public static readonly string CW = "02";
|
||||
public static readonly string CWR = "03";
|
||||
public static readonly string AM = "04";
|
||||
public static readonly string FM = "08";
|
||||
public static readonly string DIG = "0A";
|
||||
public static readonly string PKT = "0C";
|
||||
}
|
||||
|
||||
private string LockOn()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
private string LockOff()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
private string PttOn()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
private string PttOff()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
private string ClarOn()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
private string ClarOff()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
private string SplitOn()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
private string SplitOff()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
private string PowerOn()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
private string PowerOff()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
private string SetFreq(double freq)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
private string SetOpMode(OpModes opmode)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
private string SwitchVFO()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
private string SetToneMode(ToneMode mode)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
private string GetRXStatus()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
private string GetTXStatus()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
private string GetFreqAndModeStatus()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
{
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
using System;
|
||||
using System.IO.Ports;
|
||||
|
||||
namespace SharpCATLib
|
||||
{
|
||||
public class Serial
|
||||
{
|
||||
private SerialPort _serialPort;
|
||||
|
||||
//Init
|
||||
public Serial(string portname, SharpCAT.BaudRates baudrate, Parity parity, StopBits bits, Handshake handshake)
|
||||
{
|
||||
_serialPort = new SerialPort
|
||||
{
|
||||
ReadTimeout = 500,
|
||||
WriteTimeout = 500,
|
||||
PortName = portname,
|
||||
BaudRate = (int)baudrate,
|
||||
Parity = parity,
|
||||
StopBits = bits,
|
||||
Handshake = handshake
|
||||
};
|
||||
|
||||
_serialPort.DataReceived += new SerialDataReceivedEventHandler(SerialDataReceived);
|
||||
_serialPort.ErrorReceived += new SerialErrorReceivedEventHandler(SerialErrorReceived);
|
||||
}
|
||||
|
||||
private void SerialErrorReceived(object sender, SerialErrorReceivedEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
private void SerialDataReceived(object sender, SerialDataReceivedEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
public void ProbeSerialPort(SerialPort port)
|
||||
{
|
||||
}
|
||||
|
||||
public void Read()
|
||||
{
|
||||
try
|
||||
{
|
||||
string message = _serialPort.ReadLine();
|
||||
//Console.WriteLine(message);
|
||||
}
|
||||
catch (TimeoutException) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
namespace SharpCATLib
|
||||
{
|
||||
internal class SerialClient
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
namespace SharpCATLib
|
||||
{
|
||||
internal class SerialServer
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO.Ports;
|
||||
|
||||
namespace SharpCATLib
|
||||
{
|
||||
public class SharpCAT
|
||||
{
|
||||
private delegate string OnPortsSelected(string[] portnames);
|
||||
|
||||
private static event OnPortsSelected PortsSelected;
|
||||
|
||||
public SharpCAT() => SharpCAT.PortsSelected += new OnPortsSelected(ConnectPorts);
|
||||
|
||||
public string[] PortsToUse { get; set; }
|
||||
|
||||
public double[] CTCSSTones = { 67.0, 69.3, 71.9, 74.4, 77.0, 79.7, 82.5, 85.4, 88.5,
|
||||
91.5, 94.8, 97.4, 100.0, 103.5, 107.2, 110.9, 114.8, 118.8, 123, 127.3, 131.8,
|
||||
136.5, 141.3, 146.2, 151.4, 156.7, 162.2, 167.9, 173.8, 179.9, 186.2, 192.8,
|
||||
203.5, 210.7, 218.1, 225.7, 233.6, 241.8, 250.3 };
|
||||
|
||||
public int[] DCSCodes = { 023, 025, 026, 031, 032, 036, 043, 047, 051, 053, 054, 065,
|
||||
071, 072, 073, 074, 114, 115, 116, 122, 125, 131, 132, 134, 143, 145, 152, 155,
|
||||
156, 162, 165, 172, 174, 205, 212, 223, 225, 226, 243, 244, 245, 246, 251, 252, 255,
|
||||
261, 263, 265, 266, 271, 274, 306, 311, 315, 325, 331, 332, 343, 346, 351, 356, 364,
|
||||
365, 371, 411, 412, 413, 423, 431, 432, 445, 446, 452, 454, 455, 462, 464, 465, 466,
|
||||
503, 506, 516, 523, 526, 532, 546, 565, 606, 612, 624, 627, 631, 632, 654, 662, 664,
|
||||
703, 712, 723, 731, 732, 734, 743, 754 };
|
||||
|
||||
public string[] PortNames { get => SerialPort.GetPortNames(); }
|
||||
|
||||
public enum BaudRates : int { Twelve = 1200, TwentyFour = 2400, FourtyEight = 4800, NinteySix = 9600, NineteenTwo = 19200, ThirtyEightFour = 38400 };
|
||||
|
||||
public static int[] DataBits { get; } = new int[] { 7, 8 };
|
||||
|
||||
public static string[] RadioTypes { get; } = new string[] { "CAT", "CIV" };
|
||||
|
||||
private string ConnectPorts(string[] portnames)
|
||||
{
|
||||
List<SerialPort> ports = new List<SerialPort>();
|
||||
|
||||
foreach (string port in portnames)
|
||||
{
|
||||
//Testing
|
||||
ports.Add(new SerialPort(port, (int)BaudRates.ThirtyEightFour, Parity.None, (int)StopBits.Two, (int)Handshake.None));
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<LangVersion>default</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<LangVersion>default</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Radios\Icom\ID4100a.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Radios\Icom\ID880H.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Radios\Kenwood\THD74A.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Radios\Yaesu\FT818.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="SharpCAT WPF\App.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="SharpCAT WPF\MainWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.IO.Ports" Version="5.0.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
namespace SharpCATLib
|
||||
{
|
||||
internal class Socket
|
||||
{
|
||||
}
|
||||
}
|
||||
2
src/SharpCAT.Client/Program.cs
Normal file
2
src/SharpCAT.Client/Program.cs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
// See https://aka.ms/new-console-template for more information
|
||||
Console.WriteLine("Hello, World!");
|
||||
24
src/SharpCAT.Client/SharpCAT.Client.csproj
Normal file
24
src/SharpCAT.Client/SharpCAT.Client.csproj
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../SharpCAT.Common/SharpCAT.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
42
src/SharpCAT.Common/CAT/CATCommand.cs
Normal file
42
src/SharpCAT.Common/CAT/CATCommand.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
namespace SharpCAT.Common.CAT;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a CAT (Computer Aided Transceiver) command
|
||||
/// </summary>
|
||||
public class CATCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique identifier for the command
|
||||
/// </summary>
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Human-readable name of the command
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Description of what the command does
|
||||
/// </summary>
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The raw command bytes to send to the radio
|
||||
/// </summary>
|
||||
public byte[] CommandBytes { get; set; } = Array.Empty<byte>();
|
||||
|
||||
/// <summary>
|
||||
/// Expected response pattern (if any)
|
||||
/// </summary>
|
||||
public byte[]? ExpectedResponse { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parameters that can be included with the command
|
||||
/// </summary>
|
||||
public Dictionary<string, object> Parameters { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Command timeout in milliseconds
|
||||
/// </summary>
|
||||
public int TimeoutMs { get; set; } = 1000;
|
||||
}
|
||||
42
src/SharpCAT.Common/CAT/CATResponse.cs
Normal file
42
src/SharpCAT.Common/CAT/CATResponse.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
namespace SharpCAT.Common.CAT;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the response from a CAT command
|
||||
/// </summary>
|
||||
public class CATResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// The original command that generated this response
|
||||
/// </summary>
|
||||
public CATCommand Command { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether the command was successful
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Raw response bytes from the radio
|
||||
/// </summary>
|
||||
public byte[] ResponseBytes { get; set; } = Array.Empty<byte>();
|
||||
|
||||
/// <summary>
|
||||
/// Parsed response data (if applicable)
|
||||
/// </summary>
|
||||
public Dictionary<string, object> Data { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Error message if the command failed
|
||||
/// </summary>
|
||||
public string? ErrorMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Timestamp when the response was received
|
||||
/// </summary>
|
||||
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
/// Duration it took to receive the response
|
||||
/// </summary>
|
||||
public TimeSpan Duration { get; set; }
|
||||
}
|
||||
37
src/SharpCAT.Common/CAT/ICATInterface.cs
Normal file
37
src/SharpCAT.Common/CAT/ICATInterface.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
namespace SharpCAT.Common.CAT;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for CAT communication
|
||||
/// </summary>
|
||||
public interface ICATInterface
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends a CAT command and waits for a response
|
||||
/// </summary>
|
||||
/// <param name="command">The command to send</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>The response from the radio</returns>
|
||||
Task<CATResponse> SendCommandAsync(CATCommand command, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a CAT command without waiting for a response
|
||||
/// </summary>
|
||||
/// <param name="command">The command to send</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
Task SendCommandOnlyAsync(CATCommand command, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the CAT interface is connected and ready
|
||||
/// </summary>
|
||||
bool IsConnected { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when a response is received
|
||||
/// </summary>
|
||||
event EventHandler<CATResponse>? ResponseReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when an error occurs
|
||||
/// </summary>
|
||||
event EventHandler<string>? ErrorOccurred;
|
||||
}
|
||||
259
src/SharpCAT.Common/Certificates/CertificateManager.cs
Normal file
259
src/SharpCAT.Common/Certificates/CertificateManager.cs
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using SharpCAT.Common.Models;
|
||||
|
||||
namespace SharpCAT.Common.Certificates;
|
||||
|
||||
/// <summary>
|
||||
/// Certificate manager for TLS mutual authentication
|
||||
/// </summary>
|
||||
public class CertificateManager
|
||||
{
|
||||
private readonly CertificateConfig _config;
|
||||
|
||||
public CertificateManager(CertificateConfig config)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures all required certificates exist, generating them if necessary
|
||||
/// </summary>
|
||||
public async Task EnsureCertificatesExistAsync()
|
||||
{
|
||||
// Create certificate directory if it doesn't exist
|
||||
if (!Directory.Exists(_config.CertificateStorePath))
|
||||
{
|
||||
Directory.CreateDirectory(_config.CertificateStorePath);
|
||||
}
|
||||
|
||||
// Generate CA certificate if it doesn't exist
|
||||
var caCertPath = GetCACertificatePath();
|
||||
var caKeyPath = GetCAKeyPath();
|
||||
|
||||
if (!File.Exists(caCertPath) || !File.Exists(caKeyPath))
|
||||
{
|
||||
await GenerateCACertificateAsync();
|
||||
}
|
||||
|
||||
// Generate server certificate if it doesn't exist
|
||||
var serverCertPath = GetServerCertificatePath();
|
||||
var serverKeyPath = GetServerKeyPath();
|
||||
|
||||
if (!File.Exists(serverCertPath) || !File.Exists(serverKeyPath))
|
||||
{
|
||||
await GenerateServerCertificateAsync();
|
||||
}
|
||||
|
||||
// Generate client certificate if it doesn't exist
|
||||
var clientCertPath = GetClientCertificatePath();
|
||||
var clientKeyPath = GetClientKeyPath();
|
||||
|
||||
if (!File.Exists(clientCertPath) || !File.Exists(clientKeyPath))
|
||||
{
|
||||
await GenerateClientCertificateAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the CA certificate
|
||||
/// </summary>
|
||||
public X509Certificate2 LoadCACertificate()
|
||||
{
|
||||
var certPath = GetCACertificatePath();
|
||||
if (!File.Exists(certPath))
|
||||
throw new FileNotFoundException($"CA certificate not found at {certPath}");
|
||||
|
||||
return new X509Certificate2(certPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the server certificate with private key
|
||||
/// </summary>
|
||||
public X509Certificate2 LoadServerCertificate()
|
||||
{
|
||||
var certPath = GetServerCertificatePath();
|
||||
var keyPath = GetServerKeyPath();
|
||||
|
||||
if (!File.Exists(certPath))
|
||||
throw new FileNotFoundException($"Server certificate not found at {certPath}");
|
||||
|
||||
if (!File.Exists(keyPath))
|
||||
throw new FileNotFoundException($"Server private key not found at {keyPath}");
|
||||
|
||||
return LoadCertificateWithKey(certPath, keyPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the client certificate with private key
|
||||
/// </summary>
|
||||
public X509Certificate2 LoadClientCertificate()
|
||||
{
|
||||
var certPath = GetClientCertificatePath();
|
||||
var keyPath = GetClientKeyPath();
|
||||
|
||||
if (!File.Exists(certPath))
|
||||
throw new FileNotFoundException($"Client certificate not found at {certPath}");
|
||||
|
||||
if (!File.Exists(keyPath))
|
||||
throw new FileNotFoundException($"Client private key not found at {keyPath}");
|
||||
|
||||
return LoadCertificateWithKey(certPath, keyPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates a certificate against the CA
|
||||
/// </summary>
|
||||
public bool ValidateCertificate(X509Certificate2 certificate)
|
||||
{
|
||||
try
|
||||
{
|
||||
var caCert = LoadCACertificate();
|
||||
var chain = new X509Chain();
|
||||
chain.ChainPolicy.ExtraStore.Add(caCert);
|
||||
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
|
||||
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
|
||||
|
||||
return chain.Build(certificate);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GenerateCACertificateAsync()
|
||||
{
|
||||
using var rsa = RSA.Create(2048);
|
||||
var request = new CertificateRequest("CN=SharpCAT-CA", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
|
||||
// Add extensions for CA certificate
|
||||
request.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true));
|
||||
request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyCertSign | X509KeyUsageFlags.CrlSign, true));
|
||||
|
||||
var certificate = request.CreateSelfSigned(
|
||||
DateTimeOffset.Now.AddDays(-1),
|
||||
DateTimeOffset.Now.AddDays(_config.ValidityDays));
|
||||
|
||||
// Save certificate
|
||||
var certBytes = certificate.Export(X509ContentType.Cert);
|
||||
await File.WriteAllBytesAsync(GetCACertificatePath(), certBytes);
|
||||
|
||||
// Save private key
|
||||
var keyBytes = rsa.ExportRSAPrivateKey();
|
||||
var keyPem = ConvertToPem(keyBytes, "RSA PRIVATE KEY");
|
||||
await File.WriteAllTextAsync(GetCAKeyPath(), keyPem);
|
||||
}
|
||||
|
||||
private async Task GenerateServerCertificateAsync()
|
||||
{
|
||||
var caCert = LoadCACertificate();
|
||||
var caKey = LoadPrivateKey(GetCAKeyPath());
|
||||
|
||||
using var rsa = RSA.Create(2048);
|
||||
var request = new CertificateRequest("CN=SharpCAT-Server", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
|
||||
// Add extensions for server certificate
|
||||
request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment, true));
|
||||
request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") }, true)); // Server Authentication
|
||||
|
||||
var certificate = request.Create(caCert, DateTimeOffset.Now.AddDays(-1), DateTimeOffset.Now.AddDays(_config.ValidityDays), new byte[4]);
|
||||
|
||||
// Save certificate
|
||||
var certBytes = certificate.Export(X509ContentType.Cert);
|
||||
await File.WriteAllBytesAsync(GetServerCertificatePath(), certBytes);
|
||||
|
||||
// Save private key
|
||||
var keyBytes = rsa.ExportRSAPrivateKey();
|
||||
var keyPem = ConvertToPem(keyBytes, "RSA PRIVATE KEY");
|
||||
await File.WriteAllTextAsync(GetServerKeyPath(), keyPem);
|
||||
}
|
||||
|
||||
private async Task GenerateClientCertificateAsync()
|
||||
{
|
||||
var caCert = LoadCACertificate();
|
||||
var caKey = LoadPrivateKey(GetCAKeyPath());
|
||||
|
||||
using var rsa = RSA.Create(2048);
|
||||
var request = new CertificateRequest("CN=SharpCAT-Client", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
|
||||
// Add extensions for client certificate
|
||||
request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, true));
|
||||
request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid("1.3.6.1.5.5.7.3.2") }, true)); // Client Authentication
|
||||
|
||||
var certificate = request.Create(caCert, DateTimeOffset.Now.AddDays(-1), DateTimeOffset.Now.AddDays(_config.ValidityDays), new byte[4]);
|
||||
|
||||
// Save certificate
|
||||
var certBytes = certificate.Export(X509ContentType.Cert);
|
||||
await File.WriteAllBytesAsync(GetClientCertificatePath(), certBytes);
|
||||
|
||||
// Save private key
|
||||
var keyBytes = rsa.ExportRSAPrivateKey();
|
||||
var keyPem = ConvertToPem(keyBytes, "RSA PRIVATE KEY");
|
||||
await File.WriteAllTextAsync(GetClientKeyPath(), keyPem);
|
||||
}
|
||||
|
||||
private X509Certificate2 LoadCertificateWithKey(string certPath, string keyPath)
|
||||
{
|
||||
var cert = new X509Certificate2(certPath);
|
||||
var key = LoadPrivateKey(keyPath);
|
||||
return cert.CopyWithPrivateKey(key);
|
||||
}
|
||||
|
||||
private RSA LoadPrivateKey(string keyPath)
|
||||
{
|
||||
var keyPem = File.ReadAllText(keyPath);
|
||||
var keyBytes = ConvertFromPem(keyPem, "RSA PRIVATE KEY");
|
||||
|
||||
var rsa = RSA.Create();
|
||||
rsa.ImportRSAPrivateKey(keyBytes, out _);
|
||||
return rsa;
|
||||
}
|
||||
|
||||
private static string ConvertToPem(byte[] data, string type)
|
||||
{
|
||||
var base64 = Convert.ToBase64String(data);
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"-----BEGIN {type}-----");
|
||||
|
||||
for (int i = 0; i < base64.Length; i += 64)
|
||||
{
|
||||
var length = Math.Min(64, base64.Length - i);
|
||||
sb.AppendLine(base64.Substring(i, length));
|
||||
}
|
||||
|
||||
sb.AppendLine($"-----END {type}-----");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static byte[] ConvertFromPem(string pem, string type)
|
||||
{
|
||||
var startMarker = $"-----BEGIN {type}-----";
|
||||
var endMarker = $"-----END {type}-----";
|
||||
|
||||
var start = pem.IndexOf(startMarker) + startMarker.Length;
|
||||
var end = pem.IndexOf(endMarker);
|
||||
|
||||
var base64 = pem.Substring(start, end - start).Trim();
|
||||
return Convert.FromBase64String(base64);
|
||||
}
|
||||
|
||||
private string GetCACertificatePath() =>
|
||||
_config.CaCertificatePath ?? Path.Combine(_config.CertificateStorePath, "ca.crt");
|
||||
|
||||
private string GetCAKeyPath() =>
|
||||
Path.Combine(_config.CertificateStorePath, "ca.key");
|
||||
|
||||
private string GetServerCertificatePath() =>
|
||||
_config.ServerCertificatePath ?? Path.Combine(_config.CertificateStorePath, "server.crt");
|
||||
|
||||
private string GetServerKeyPath() =>
|
||||
_config.ServerKeyPath ?? Path.Combine(_config.CertificateStorePath, "server.key");
|
||||
|
||||
private string GetClientCertificatePath() =>
|
||||
_config.ClientCertificatePath ?? Path.Combine(_config.CertificateStorePath, "client.crt");
|
||||
|
||||
private string GetClientKeyPath() =>
|
||||
_config.ClientKeyPath ?? Path.Combine(_config.CertificateStorePath, "client.key");
|
||||
}
|
||||
173
src/SharpCAT.Common/Configuration/ConfigurationLoader.cs
Normal file
173
src/SharpCAT.Common/Configuration/ConfigurationLoader.cs
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Newtonsoft.Json;
|
||||
using SharpCAT.Common.Models;
|
||||
|
||||
namespace SharpCAT.Common.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration loader for SharpCAT applications
|
||||
/// </summary>
|
||||
public static class ConfigurationLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads server configuration from JSON file
|
||||
/// </summary>
|
||||
/// <param name="configPath">Path to the configuration file</param>
|
||||
/// <returns>Server configuration</returns>
|
||||
public static ServerConfig LoadServerConfig(string configPath = "server-config.json")
|
||||
{
|
||||
return LoadConfig<ServerConfig>(configPath, GetDefaultServerConfig());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads client configuration from JSON file
|
||||
/// </summary>
|
||||
/// <param name="configPath">Path to the configuration file</param>
|
||||
/// <returns>Client configuration</returns>
|
||||
public static ClientConfig LoadClientConfig(string configPath = "client-config.json")
|
||||
{
|
||||
return LoadConfig<ClientConfig>(configPath, GetDefaultClientConfig());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves server configuration to JSON file
|
||||
/// </summary>
|
||||
/// <param name="config">Configuration to save</param>
|
||||
/// <param name="configPath">Path to save the configuration file</param>
|
||||
public static async Task SaveServerConfigAsync(ServerConfig config, string configPath = "server-config.json")
|
||||
{
|
||||
await SaveConfigAsync(config, configPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves client configuration to JSON file
|
||||
/// </summary>
|
||||
/// <param name="config">Configuration to save</param>
|
||||
/// <param name="configPath">Path to save the configuration file</param>
|
||||
public static async Task SaveClientConfigAsync(ClientConfig config, string configPath = "client-config.json")
|
||||
{
|
||||
await SaveConfigAsync(config, configPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates default server configuration file if it doesn't exist
|
||||
/// </summary>
|
||||
/// <param name="configPath">Path to create the configuration file</param>
|
||||
public static async Task EnsureServerConfigExistsAsync(string configPath = "server-config.json")
|
||||
{
|
||||
if (!File.Exists(configPath))
|
||||
{
|
||||
await SaveServerConfigAsync(GetDefaultServerConfig(), configPath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates default client configuration file if it doesn't exist
|
||||
/// </summary>
|
||||
/// <param name="configPath">Path to create the configuration file</param>
|
||||
public static async Task EnsureClientConfigExistsAsync(string configPath = "client-config.json")
|
||||
{
|
||||
if (!File.Exists(configPath))
|
||||
{
|
||||
await SaveClientConfigAsync(GetDefaultClientConfig(), configPath);
|
||||
}
|
||||
}
|
||||
|
||||
private static T LoadConfig<T>(string configPath, T defaultConfig) where T : class
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(configPath))
|
||||
{
|
||||
// Create default configuration file
|
||||
var defaultJson = JsonConvert.SerializeObject(defaultConfig, Formatting.Indented);
|
||||
File.WriteAllText(configPath, defaultJson);
|
||||
return defaultConfig;
|
||||
}
|
||||
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddJsonFile(configPath, optional: false, reloadOnChange: false)
|
||||
.Build();
|
||||
|
||||
var config = configuration.Get<T>();
|
||||
return config ?? defaultConfig;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to load configuration from {configPath}: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task SaveConfigAsync<T>(T config, string configPath) where T : class
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(config, Formatting.Indented);
|
||||
await File.WriteAllTextAsync(configPath, json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to save configuration to {configPath}: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static ServerConfig GetDefaultServerConfig()
|
||||
{
|
||||
return new ServerConfig
|
||||
{
|
||||
Port = 8443,
|
||||
Certificate = new CertificateConfig
|
||||
{
|
||||
CertificateStorePath = "./certificates",
|
||||
AutoGenerateCertificates = true,
|
||||
ValidityDays = 365,
|
||||
Subject = "CN=SharpCAT-Server"
|
||||
},
|
||||
SerialPort = new SerialPortConfig
|
||||
{
|
||||
BaudRate = 9600,
|
||||
DataBits = 8,
|
||||
StopBits = System.IO.Ports.StopBits.One,
|
||||
Parity = System.IO.Ports.Parity.None,
|
||||
Handshake = System.IO.Ports.Handshake.None,
|
||||
ReadTimeoutMs = 1000,
|
||||
WriteTimeoutMs = 1000,
|
||||
AutoDetectPort = true
|
||||
},
|
||||
Logging = new LoggingConfig
|
||||
{
|
||||
LogLevel = "Information",
|
||||
EnableConsoleLogging = true,
|
||||
EnableOSLogging = true,
|
||||
IncludeTimestamps = true,
|
||||
IncludeLogLevel = true
|
||||
},
|
||||
MaxClients = 10
|
||||
};
|
||||
}
|
||||
|
||||
private static ClientConfig GetDefaultClientConfig()
|
||||
{
|
||||
return new ClientConfig
|
||||
{
|
||||
ServerHost = "localhost",
|
||||
ServerPort = 8443,
|
||||
Certificate = new CertificateConfig
|
||||
{
|
||||
CertificateStorePath = "./certificates",
|
||||
AutoGenerateCertificates = true,
|
||||
ValidityDays = 365,
|
||||
Subject = "CN=SharpCAT-Client"
|
||||
},
|
||||
Logging = new LoggingConfig
|
||||
{
|
||||
LogLevel = "Information",
|
||||
EnableConsoleLogging = true,
|
||||
EnableOSLogging = true,
|
||||
IncludeTimestamps = true,
|
||||
IncludeLogLevel = true
|
||||
},
|
||||
ConnectionTimeoutMs = 5000
|
||||
};
|
||||
}
|
||||
}
|
||||
230
src/SharpCAT.Common/Logging/SharpCATLogger.cs
Normal file
230
src/SharpCAT.Common/Logging/SharpCATLogger.cs
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace SharpCAT.Common.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// Custom logger provider that supports both console and native OS logging
|
||||
/// </summary>
|
||||
public class SharpCATLoggerProvider : ILoggerProvider
|
||||
{
|
||||
private readonly bool _enableConsoleLogging;
|
||||
private readonly bool _enableOSLogging;
|
||||
private readonly string _applicationName;
|
||||
|
||||
public SharpCATLoggerProvider(bool enableConsoleLogging = true, bool enableOSLogging = true, string applicationName = "SharpCAT")
|
||||
{
|
||||
_enableConsoleLogging = enableConsoleLogging;
|
||||
_enableOSLogging = enableOSLogging;
|
||||
_applicationName = applicationName;
|
||||
}
|
||||
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
return new SharpCATLogger(categoryName, _enableConsoleLogging, _enableOSLogging, _applicationName);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Nothing to dispose
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom logger that supports both console and native OS logging
|
||||
/// </summary>
|
||||
public class SharpCATLogger : ILogger
|
||||
{
|
||||
private readonly string _categoryName;
|
||||
private readonly bool _enableConsoleLogging;
|
||||
private readonly bool _enableOSLogging;
|
||||
private readonly string _applicationName;
|
||||
private readonly EventLog? _eventLog;
|
||||
|
||||
public SharpCATLogger(string categoryName, bool enableConsoleLogging, bool enableOSLogging, string applicationName)
|
||||
{
|
||||
_categoryName = categoryName;
|
||||
_enableConsoleLogging = enableConsoleLogging;
|
||||
_enableOSLogging = enableOSLogging;
|
||||
_applicationName = applicationName;
|
||||
|
||||
// Initialize Event Log for Windows
|
||||
if (_enableOSLogging && RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
try
|
||||
{
|
||||
_eventLog = new EventLog();
|
||||
_eventLog.Source = _applicationName;
|
||||
|
||||
// Create event source if it doesn't exist
|
||||
if (!EventLog.SourceExists(_applicationName))
|
||||
{
|
||||
EventLog.CreateEventSource(_applicationName, "Application");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If we can't create event log, just disable OS logging
|
||||
_eventLog = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state) => NullScope.Instance;
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
return logLevel != LogLevel.None;
|
||||
}
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
if (!IsEnabled(logLevel))
|
||||
return;
|
||||
|
||||
var message = formatter(state, exception);
|
||||
|
||||
if (exception != null)
|
||||
message += Environment.NewLine + exception.ToString();
|
||||
|
||||
// Console logging
|
||||
if (_enableConsoleLogging)
|
||||
{
|
||||
LogToConsole(logLevel, message);
|
||||
}
|
||||
|
||||
// OS logging
|
||||
if (_enableOSLogging)
|
||||
{
|
||||
LogToOS(logLevel, message);
|
||||
}
|
||||
}
|
||||
|
||||
private void LogToConsole(LogLevel logLevel, string message)
|
||||
{
|
||||
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
||||
var level = GetLogLevelString(logLevel);
|
||||
var formattedMessage = $"[{timestamp}] [{level}] [{_categoryName}] {message}";
|
||||
|
||||
// Set console color based on log level
|
||||
var originalColor = Console.ForegroundColor;
|
||||
Console.ForegroundColor = GetConsoleColor(logLevel);
|
||||
|
||||
Console.WriteLine(formattedMessage);
|
||||
|
||||
Console.ForegroundColor = originalColor;
|
||||
}
|
||||
|
||||
private void LogToOS(LogLevel logLevel, string message)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
LogToWindowsEventLog(logLevel, message);
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
LogToSyslog(logLevel, message);
|
||||
}
|
||||
}
|
||||
|
||||
private void LogToWindowsEventLog(LogLevel logLevel, string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
_eventLog?.WriteEntry($"[{_categoryName}] {message}", GetEventLogEntryType(logLevel));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors writing to event log
|
||||
}
|
||||
}
|
||||
|
||||
private void LogToSyslog(LogLevel logLevel, string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Use logger command to write to syslog
|
||||
var priority = GetSyslogPriority(logLevel);
|
||||
var formattedMessage = $"[{_categoryName}] {message}";
|
||||
|
||||
// Escape message for shell
|
||||
var escapedMessage = formattedMessage.Replace("\"", "\\\"");
|
||||
|
||||
using var process = new Process();
|
||||
process.StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "/usr/bin/logger",
|
||||
Arguments = $"-p {priority} -t \"{_applicationName}\" \"{escapedMessage}\"",
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
process.Start();
|
||||
process.WaitForExit(1000); // Wait max 1 second
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors writing to syslog
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetLogLevelString(LogLevel logLevel)
|
||||
{
|
||||
return logLevel switch
|
||||
{
|
||||
LogLevel.Trace => "TRACE",
|
||||
LogLevel.Debug => "DEBUG",
|
||||
LogLevel.Information => "INFO",
|
||||
LogLevel.Warning => "WARN",
|
||||
LogLevel.Error => "ERROR",
|
||||
LogLevel.Critical => "CRIT",
|
||||
_ => "UNKNOWN"
|
||||
};
|
||||
}
|
||||
|
||||
private static ConsoleColor GetConsoleColor(LogLevel logLevel)
|
||||
{
|
||||
return logLevel switch
|
||||
{
|
||||
LogLevel.Trace => ConsoleColor.Gray,
|
||||
LogLevel.Debug => ConsoleColor.Gray,
|
||||
LogLevel.Information => ConsoleColor.White,
|
||||
LogLevel.Warning => ConsoleColor.Yellow,
|
||||
LogLevel.Error => ConsoleColor.Red,
|
||||
LogLevel.Critical => ConsoleColor.DarkRed,
|
||||
_ => ConsoleColor.White
|
||||
};
|
||||
}
|
||||
|
||||
private static EventLogEntryType GetEventLogEntryType(LogLevel logLevel)
|
||||
{
|
||||
return logLevel switch
|
||||
{
|
||||
LogLevel.Warning => EventLogEntryType.Warning,
|
||||
LogLevel.Error => EventLogEntryType.Error,
|
||||
LogLevel.Critical => EventLogEntryType.Error,
|
||||
_ => EventLogEntryType.Information
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetSyslogPriority(LogLevel logLevel)
|
||||
{
|
||||
return logLevel switch
|
||||
{
|
||||
LogLevel.Trace => "user.debug",
|
||||
LogLevel.Debug => "user.debug",
|
||||
LogLevel.Information => "user.info",
|
||||
LogLevel.Warning => "user.warning",
|
||||
LogLevel.Error => "user.err",
|
||||
LogLevel.Critical => "user.crit",
|
||||
_ => "user.info"
|
||||
};
|
||||
}
|
||||
|
||||
private class NullScope : IDisposable
|
||||
{
|
||||
public static readonly NullScope Instance = new();
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
52
src/SharpCAT.Common/Models/CertificateConfig.cs
Normal file
52
src/SharpCAT.Common/Models/CertificateConfig.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
namespace SharpCAT.Common.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Certificate configuration for TLS mutual authentication
|
||||
/// </summary>
|
||||
public class CertificateConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Path to the Certificate Authority (CA) certificate file
|
||||
/// </summary>
|
||||
public string? CaCertificatePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Path to the server certificate file
|
||||
/// </summary>
|
||||
public string? ServerCertificatePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Path to the server private key file
|
||||
/// </summary>
|
||||
public string? ServerKeyPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Path to the client certificate file
|
||||
/// </summary>
|
||||
public string? ClientCertificatePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Path to the client private key file
|
||||
/// </summary>
|
||||
public string? ClientKeyPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Certificate store location for storing generated certificates
|
||||
/// </summary>
|
||||
public string CertificateStorePath { get; set; } = "./certificates";
|
||||
|
||||
/// <summary>
|
||||
/// Whether to auto-generate certificates if they don't exist
|
||||
/// </summary>
|
||||
public bool AutoGenerateCertificates { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Certificate validity period in days
|
||||
/// </summary>
|
||||
public int ValidityDays { get; set; } = 365;
|
||||
|
||||
/// <summary>
|
||||
/// Subject name for generated certificates
|
||||
/// </summary>
|
||||
public string Subject { get; set; } = "CN=SharpCAT";
|
||||
}
|
||||
32
src/SharpCAT.Common/Models/ClientConfig.cs
Normal file
32
src/SharpCAT.Common/Models/ClientConfig.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
namespace SharpCAT.Common.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration model for the SharpCAT client
|
||||
/// </summary>
|
||||
public class ClientConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Server hostname or IP address to connect to
|
||||
/// </summary>
|
||||
public string ServerHost { get; set; } = "localhost";
|
||||
|
||||
/// <summary>
|
||||
/// Server port to connect to
|
||||
/// </summary>
|
||||
public int ServerPort { get; set; } = 8443;
|
||||
|
||||
/// <summary>
|
||||
/// Certificate configuration for TLS
|
||||
/// </summary>
|
||||
public CertificateConfig Certificate { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Logging configuration
|
||||
/// </summary>
|
||||
public LoggingConfig Logging { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Connection timeout in milliseconds
|
||||
/// </summary>
|
||||
public int ConnectionTimeoutMs { get; set; } = 5000;
|
||||
}
|
||||
37
src/SharpCAT.Common/Models/LoggingConfig.cs
Normal file
37
src/SharpCAT.Common/Models/LoggingConfig.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
namespace SharpCAT.Common.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Logging configuration
|
||||
/// </summary>
|
||||
public class LoggingConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Minimum log level
|
||||
/// </summary>
|
||||
public string LogLevel { get; set; } = "Information";
|
||||
|
||||
/// <summary>
|
||||
/// Whether to enable console logging
|
||||
/// </summary>
|
||||
public bool EnableConsoleLogging { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to enable native OS logging (Event Log on Windows, syslog on Linux/macOS)
|
||||
/// </summary>
|
||||
public bool EnableOSLogging { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Log file path (optional)
|
||||
/// </summary>
|
||||
public string? LogFilePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to include timestamps in console output
|
||||
/// </summary>
|
||||
public bool IncludeTimestamps { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to include log levels in console output
|
||||
/// </summary>
|
||||
public bool IncludeLogLevel { get; set; } = true;
|
||||
}
|
||||
52
src/SharpCAT.Common/Models/SerialPortConfig.cs
Normal file
52
src/SharpCAT.Common/Models/SerialPortConfig.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
namespace SharpCAT.Common.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Serial port configuration for CAT communication
|
||||
/// </summary>
|
||||
public class SerialPortConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Serial port name (e.g., COM1, /dev/ttyUSB0)
|
||||
/// </summary>
|
||||
public string? PortName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Baud rate for serial communication
|
||||
/// </summary>
|
||||
public int BaudRate { get; set; } = 9600;
|
||||
|
||||
/// <summary>
|
||||
/// Data bits
|
||||
/// </summary>
|
||||
public int DataBits { get; set; } = 8;
|
||||
|
||||
/// <summary>
|
||||
/// Stop bits
|
||||
/// </summary>
|
||||
public System.IO.Ports.StopBits StopBits { get; set; } = System.IO.Ports.StopBits.One;
|
||||
|
||||
/// <summary>
|
||||
/// Parity setting
|
||||
/// </summary>
|
||||
public System.IO.Ports.Parity Parity { get; set; } = System.IO.Ports.Parity.None;
|
||||
|
||||
/// <summary>
|
||||
/// Flow control/handshake
|
||||
/// </summary>
|
||||
public System.IO.Ports.Handshake Handshake { get; set; } = System.IO.Ports.Handshake.None;
|
||||
|
||||
/// <summary>
|
||||
/// Read timeout in milliseconds
|
||||
/// </summary>
|
||||
public int ReadTimeoutMs { get; set; } = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// Write timeout in milliseconds
|
||||
/// </summary>
|
||||
public int WriteTimeoutMs { get; set; } = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to auto-detect the serial port if not specified
|
||||
/// </summary>
|
||||
public bool AutoDetectPort { get; set; } = true;
|
||||
}
|
||||
32
src/SharpCAT.Common/Models/ServerConfig.cs
Normal file
32
src/SharpCAT.Common/Models/ServerConfig.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
namespace SharpCAT.Common.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration model for the SharpCAT server
|
||||
/// </summary>
|
||||
public class ServerConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// TCP port to listen on
|
||||
/// </summary>
|
||||
public int Port { get; set; } = 8443;
|
||||
|
||||
/// <summary>
|
||||
/// Certificate configuration for TLS
|
||||
/// </summary>
|
||||
public CertificateConfig Certificate { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Serial port configuration for CAT communication
|
||||
/// </summary>
|
||||
public SerialPortConfig SerialPort { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Logging configuration
|
||||
/// </summary>
|
||||
public LoggingConfig Logging { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of concurrent clients
|
||||
/// </summary>
|
||||
public int MaxClients { get; set; } = 10;
|
||||
}
|
||||
173
src/SharpCAT.Common/SerialPort/SerialPortHelper.cs
Normal file
173
src/SharpCAT.Common/SerialPort/SerialPortHelper.cs
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
using System.IO.Ports;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SharpCAT.Common.SerialPort;
|
||||
|
||||
/// <summary>
|
||||
/// Cross-platform serial port helper utilities
|
||||
/// </summary>
|
||||
public static class SerialPortHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets available serial port names for the current platform
|
||||
/// </summary>
|
||||
/// <returns>Array of available serial port names</returns>
|
||||
public static string[] GetAvailablePortNames()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return GetWindowsPortNames();
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
return GetLinuxPortNames();
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
return GetMacOSPortNames();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to .NET standard method
|
||||
return System.IO.Ports.SerialPort.GetPortNames();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback to .NET standard method on any error
|
||||
return System.IO.Ports.SerialPort.GetPortNames();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto-detects the best serial port for CAT communication
|
||||
/// </summary>
|
||||
/// <returns>The detected port name, or null if none found</returns>
|
||||
public static async Task<string?> AutoDetectCATPortAsync()
|
||||
{
|
||||
var ports = GetAvailablePortNames();
|
||||
|
||||
foreach (var portName in ports)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var port = new System.IO.Ports.SerialPort(portName, 9600, Parity.None, 8, StopBits.One);
|
||||
port.ReadTimeout = 1000;
|
||||
port.WriteTimeout = 1000;
|
||||
|
||||
port.Open();
|
||||
|
||||
// Try a simple CAT command (may vary by radio)
|
||||
// This is a basic "ID" command that many radios support
|
||||
await Task.Delay(100); // Allow port to stabilize
|
||||
|
||||
port.Close();
|
||||
|
||||
// If we get here without exception, the port is likely valid
|
||||
return portName;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Port is not available or doesn't respond - continue to next
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string[] GetWindowsPortNames()
|
||||
{
|
||||
// Windows COM ports
|
||||
var ports = new List<string>();
|
||||
for (int i = 1; i <= 256; i++)
|
||||
{
|
||||
string portName = $"COM{i}";
|
||||
try
|
||||
{
|
||||
using var port = new System.IO.Ports.SerialPort(portName);
|
||||
ports.Add(portName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Port doesn't exist
|
||||
}
|
||||
}
|
||||
return ports.ToArray();
|
||||
}
|
||||
|
||||
private static string[] GetLinuxPortNames()
|
||||
{
|
||||
// Linux serial devices
|
||||
var ports = new List<string>();
|
||||
|
||||
// Standard serial ports
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
string portName = $"/dev/ttyS{i}";
|
||||
if (File.Exists(portName))
|
||||
ports.Add(portName);
|
||||
}
|
||||
|
||||
// USB serial ports
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
string portName = $"/dev/ttyUSB{i}";
|
||||
if (File.Exists(portName))
|
||||
ports.Add(portName);
|
||||
}
|
||||
|
||||
// USB ACM ports (often used by Arduino and similar devices)
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
string portName = $"/dev/ttyACM{i}";
|
||||
if (File.Exists(portName))
|
||||
ports.Add(portName);
|
||||
}
|
||||
|
||||
return ports.ToArray();
|
||||
}
|
||||
|
||||
private static string[] GetMacOSPortNames()
|
||||
{
|
||||
// macOS serial devices
|
||||
var ports = new List<string>();
|
||||
|
||||
try
|
||||
{
|
||||
// Check for USB serial devices
|
||||
var devDirectory = "/dev";
|
||||
if (Directory.Exists(devDirectory))
|
||||
{
|
||||
var files = Directory.GetFiles(devDirectory, "tty.*");
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (file.Contains("usb", StringComparison.OrdinalIgnoreCase) ||
|
||||
file.Contains("serial", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ports.Add(file);
|
||||
}
|
||||
}
|
||||
|
||||
// Also check for cu.* devices which are common on macOS
|
||||
files = Directory.GetFiles(devDirectory, "cu.*");
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (file.Contains("usb", StringComparison.OrdinalIgnoreCase) ||
|
||||
file.Contains("serial", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ports.Add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback to standard method
|
||||
}
|
||||
|
||||
return ports.ToArray();
|
||||
}
|
||||
}
|
||||
22
src/SharpCAT.Common/SharpCAT.Common.csproj
Normal file
22
src/SharpCAT.Common/SharpCAT.Common.csproj
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="6.0.0" />
|
||||
<PackageReference Include="System.IO.Ports" Version="6.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="System.Security.Cryptography.X509Certificates" Version="4.3.2" />
|
||||
<PackageReference Include="System.Diagnostics.EventLog" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
2
src/SharpCAT.Server/Program.cs
Normal file
2
src/SharpCAT.Server/Program.cs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
// See https://aka.ms/new-console-template for more information
|
||||
Console.WriteLine("Hello, World!");
|
||||
24
src/SharpCAT.Server/SharpCAT.Server.csproj
Normal file
24
src/SharpCAT.Server/SharpCAT.Server.csproj
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../SharpCAT.Common/SharpCAT.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Loading…
Reference in a new issue