2023-12-10 13:55:12 -05:00
|
|
|
|
using CollabVMAgent.Protocol;
|
|
|
|
|
using MsgPack.Serialization;
|
|
|
|
|
using System;
|
2023-12-04 11:50:21 -05:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Runtime.ConstrainedExecution;
|
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
using System.Security;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
|
|
|
|
|
namespace CollabVMAgent
|
|
|
|
|
{
|
|
|
|
|
public class VirtIOSerial : IDisposable
|
|
|
|
|
{
|
2023-12-10 13:55:12 -05:00
|
|
|
|
MessagePackSerializer serializer = MessagePackSerializer.Get<ProtocolMessage>();
|
2023-12-04 11:50:21 -05:00
|
|
|
|
const int READ_INTERVAL = 3000;
|
|
|
|
|
const int COPY_BUFFER_SIZE = 4096;
|
|
|
|
|
static Guid GUID_VIOSERIAL_PORT = new Guid(0x6fde7521, 0x1b65, 0x48ae, 0xb6, 0x28, 0x80, 0xbe, 0x62, 0x1, 0x60, 0x26);
|
|
|
|
|
static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
|
|
|
|
|
|
|
|
|
|
string DevicePath;
|
|
|
|
|
IntPtr viohnd;
|
|
|
|
|
Thread readThread;
|
|
|
|
|
CancellationTokenSource readCancelSrc;
|
|
|
|
|
CancellationToken readCancel;
|
|
|
|
|
bool disposed = false;
|
|
|
|
|
|
|
|
|
|
public event EventHandler<TypedEventArgs<byte[]>> Data;
|
|
|
|
|
public VirtIOSerial()
|
|
|
|
|
{
|
|
|
|
|
DevicePath = GetDevicePath();
|
|
|
|
|
viohnd = CreateFile(DevicePath, FileAccess.ReadWrite, 0, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero);
|
|
|
|
|
readCancelSrc = new CancellationTokenSource();
|
|
|
|
|
readCancel = readCancelSrc.Token;
|
|
|
|
|
readThread = new Thread(new ThreadStart(ReadLoop));
|
|
|
|
|
readThread.Start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool Write(byte[] data)
|
|
|
|
|
{
|
|
|
|
|
if (disposed) throw new ObjectDisposedException(nameof(VirtIOSerial));
|
|
|
|
|
uint wrote;
|
|
|
|
|
return WriteFile(viohnd, data, (uint)data.Length, out wrote, IntPtr.Zero);
|
|
|
|
|
}
|
2023-12-10 13:55:12 -05:00
|
|
|
|
|
|
|
|
|
public bool WriteMsg(ProtocolMessage msg)
|
|
|
|
|
{
|
|
|
|
|
byte[] message;
|
|
|
|
|
using (var ms = new MemoryStream())
|
|
|
|
|
{
|
|
|
|
|
serializer.Pack(ms, msg);
|
|
|
|
|
message = ms.ToArray();
|
|
|
|
|
}
|
|
|
|
|
byte[] header = BitConverter.GetBytes((UInt32)message.Length);
|
|
|
|
|
if (!Write(header)) return false;
|
|
|
|
|
return Write(message);
|
|
|
|
|
}
|
2023-12-04 11:50:21 -05:00
|
|
|
|
|
|
|
|
|
void ReadLoop()
|
|
|
|
|
{
|
|
|
|
|
byte[] sizebuf = new byte[4];
|
|
|
|
|
while (!readCancel.IsCancellationRequested)
|
|
|
|
|
{
|
|
|
|
|
// Get the length of the message
|
|
|
|
|
uint read = 0;
|
|
|
|
|
while (read != 4)
|
|
|
|
|
{
|
|
|
|
|
if (readCancel.IsCancellationRequested) return;
|
|
|
|
|
Thread.Sleep(READ_INTERVAL);
|
|
|
|
|
ReadFile(viohnd, sizebuf, 4, out read, IntPtr.Zero);
|
2023-12-04 12:22:23 -05:00
|
|
|
|
var err = Marshal.GetLastWin32Error();
|
|
|
|
|
if (err != 0)
|
|
|
|
|
{
|
|
|
|
|
#if DEBUG
|
|
|
|
|
Console.WriteLine($"Got error {err} while trying to read from serial port");
|
|
|
|
|
#endif
|
|
|
|
|
switch (err)
|
|
|
|
|
{
|
|
|
|
|
case 6:
|
|
|
|
|
CloseHandle(viohnd);
|
|
|
|
|
viohnd = CreateFile(DevicePath, FileAccess.ReadWrite, 0, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-12-04 11:50:21 -05:00
|
|
|
|
}
|
|
|
|
|
uint size = BitConverter.ToUInt32(sizebuf, 0);
|
|
|
|
|
#if DEBUG
|
|
|
|
|
Console.WriteLine($"Reported payload size: {size}");
|
|
|
|
|
#endif
|
|
|
|
|
// Read the message
|
|
|
|
|
uint position = 0;
|
|
|
|
|
byte[] buf = new byte[COPY_BUFFER_SIZE];
|
|
|
|
|
MemoryStream ms = new MemoryStream();
|
|
|
|
|
while (position < size)
|
|
|
|
|
{
|
|
|
|
|
if (readCancel.IsCancellationRequested)
|
|
|
|
|
{
|
|
|
|
|
ms.Dispose();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
ReadFile(viohnd, buf, COPY_BUFFER_SIZE, out read, IntPtr.Zero);
|
|
|
|
|
if (position < size && read == 0) throw new Exception("Stream ended prematurely");
|
|
|
|
|
ms.Write(buf, 0, (int)read);
|
|
|
|
|
#if DEBUG
|
|
|
|
|
Console.WriteLine($"Bytes read: {read}, Position: {position}, Size: {size}");
|
|
|
|
|
#endif
|
|
|
|
|
position += read;
|
|
|
|
|
}
|
|
|
|
|
byte[] payload = ms.ToArray();
|
2023-12-10 13:55:12 -05:00
|
|
|
|
if (this.WriteMsg(new ProtocolMessage
|
|
|
|
|
{
|
|
|
|
|
Operation = ProtocolOperation.ACK
|
|
|
|
|
}))
|
|
|
|
|
Console.WriteLine("Wrote ACK to serial.");
|
2023-12-04 11:50:21 -05:00
|
|
|
|
ms.Dispose();
|
|
|
|
|
Data.Invoke(this, new TypedEventArgs<byte[]>(payload));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
static string GetDevicePath()
|
|
|
|
|
{
|
|
|
|
|
IntPtr HardwareDeviceInfo;
|
|
|
|
|
SP_DEVICE_INTERFACE_DATA DeviceInterfaceData = new SP_DEVICE_INTERFACE_DATA();
|
|
|
|
|
bool bResult;
|
|
|
|
|
UInt32 Length = 0;
|
|
|
|
|
UInt32 RequiredLength = 0;
|
|
|
|
|
IntPtr DeviceInterfaceDetailData;
|
|
|
|
|
|
|
|
|
|
HardwareDeviceInfo = SetupDiGetClassDevs(ref GUID_VIOSERIAL_PORT, IntPtr.Zero, IntPtr.Zero, (uint)(DIGCF.DIGCF_PRESENT | DIGCF.DIGCF_DEVICEINTERFACE));
|
|
|
|
|
if (HardwareDeviceInfo.Equals(INVALID_HANDLE_VALUE))
|
|
|
|
|
{
|
|
|
|
|
throw new Exception("Cannot get class devices.");
|
|
|
|
|
}
|
|
|
|
|
DeviceInterfaceData.cbSize = Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DATA));
|
|
|
|
|
bResult = SetupDiEnumDeviceInterfaces(HardwareDeviceInfo, IntPtr.Zero, ref GUID_VIOSERIAL_PORT, 0, ref DeviceInterfaceData);
|
|
|
|
|
if (bResult == false)
|
|
|
|
|
{
|
|
|
|
|
SetupDiDestroyDeviceInfoList(HardwareDeviceInfo);
|
|
|
|
|
throw new Exception("Cannot get enumerate device interfaces.");
|
|
|
|
|
}
|
|
|
|
|
SetupDiGetDeviceInterfaceDetail(HardwareDeviceInfo, ref DeviceInterfaceData, IntPtr.Zero, 0, ref RequiredLength, IntPtr.Zero);
|
|
|
|
|
// Skidded from stackoverflow
|
|
|
|
|
int structSize = Marshal.SystemDefaultCharSize;
|
|
|
|
|
if (IntPtr.Size == 8)
|
|
|
|
|
structSize += 6; // 64-bit systems, with 8-byte packing
|
|
|
|
|
else
|
|
|
|
|
structSize += 4; // 32-bit systems, with byte packing
|
|
|
|
|
DeviceInterfaceDetailData = Marshal.AllocHGlobal((int)RequiredLength + structSize);
|
|
|
|
|
if (DeviceInterfaceDetailData.Equals(IntPtr.Zero))
|
|
|
|
|
{
|
|
|
|
|
SetupDiDestroyDeviceInfoList(HardwareDeviceInfo);
|
|
|
|
|
throw new Exception("Cannot allocate memory.");
|
|
|
|
|
}
|
|
|
|
|
Marshal.WriteInt32(DeviceInterfaceDetailData, (int)structSize);
|
|
|
|
|
bResult = SetupDiGetDeviceInterfaceDetail(HardwareDeviceInfo, ref DeviceInterfaceData, DeviceInterfaceDetailData, RequiredLength, ref RequiredLength, IntPtr.Zero);
|
|
|
|
|
if (bResult == false)
|
|
|
|
|
{
|
|
|
|
|
SetupDiDestroyDeviceInfoList(HardwareDeviceInfo);
|
|
|
|
|
Marshal.FreeHGlobal(DeviceInterfaceDetailData);
|
|
|
|
|
throw new Exception("Cannot get device interface details.");
|
|
|
|
|
}
|
|
|
|
|
string DevicePath = Marshal.PtrToStringUni(new IntPtr(DeviceInterfaceDetailData.ToInt64() + 4));
|
|
|
|
|
Marshal.FreeHGlobal(DeviceInterfaceDetailData);
|
|
|
|
|
return DevicePath;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-04 12:22:23 -05:00
|
|
|
|
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
2023-12-04 11:50:21 -05:00
|
|
|
|
static extern IntPtr SetupDiGetClassDevs(
|
|
|
|
|
ref Guid ClassGuid,
|
|
|
|
|
IntPtr Enumerator,
|
|
|
|
|
IntPtr hwndParent,
|
|
|
|
|
uint Flags
|
|
|
|
|
);
|
|
|
|
|
[DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
|
|
|
|
static extern Boolean SetupDiEnumDeviceInterfaces(
|
|
|
|
|
IntPtr hDevInfo,
|
|
|
|
|
IntPtr devInfo,
|
|
|
|
|
ref Guid interfaceClassGuid,
|
|
|
|
|
UInt32 memberIndex,
|
|
|
|
|
ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
[DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
|
|
|
|
static extern Boolean SetupDiGetDeviceInterfaceDetail(
|
|
|
|
|
IntPtr hDevInfo,
|
|
|
|
|
ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData,
|
|
|
|
|
IntPtr deviceInterfaceDetailData,
|
|
|
|
|
UInt32 deviceInterfaceDetailDataSize,
|
|
|
|
|
ref UInt32 requiredSize,
|
|
|
|
|
IntPtr deviceInfoData
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
[DllImport("setupapi.dll", SetLastError = true)]
|
|
|
|
|
static extern bool SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);
|
|
|
|
|
|
|
|
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
|
|
|
|
static extern IntPtr CreateFile(
|
|
|
|
|
[MarshalAs(UnmanagedType.LPTStr)] string filename,
|
|
|
|
|
[MarshalAs(UnmanagedType.U4)] FileAccess access,
|
|
|
|
|
[MarshalAs(UnmanagedType.U4)] uint share,
|
|
|
|
|
IntPtr securityAttributes, // optional SECURITY_ATTRIBUTES struct or IntPtr.Zero
|
|
|
|
|
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
|
|
|
|
|
[MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes,
|
|
|
|
|
IntPtr templateFile
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
|
|
|
static extern bool ReadFile(IntPtr hFile, [Out] byte[] lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped);
|
|
|
|
|
|
2023-12-04 12:22:23 -05:00
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
2023-12-04 11:50:21 -05:00
|
|
|
|
static extern bool WriteFile(IntPtr hFile, byte[] lpBuffer, uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten, IntPtr lpOverlapped);
|
|
|
|
|
|
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
|
|
|
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
|
|
|
|
|
[SuppressUnmanagedCodeSecurity]
|
|
|
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
|
|
|
static extern bool CloseHandle(IntPtr hObject);
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
this.disposed = true;
|
|
|
|
|
readCancelSrc.Cancel();
|
|
|
|
|
readCancelSrc.Dispose();
|
|
|
|
|
CloseHandle(viohnd);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
|
|
|
struct SP_DEVICE_INTERFACE_DATA
|
|
|
|
|
{
|
|
|
|
|
public Int32 cbSize;
|
|
|
|
|
public Guid interfaceClassGuid;
|
|
|
|
|
public Int32 flags;
|
|
|
|
|
private UIntPtr reserved;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Flags]
|
|
|
|
|
public enum DIGCF : uint
|
|
|
|
|
{
|
|
|
|
|
DIGCF_DEFAULT = 0x00000001, // only valid with DIGCF_DEVICEINTERFACE
|
|
|
|
|
DIGCF_PRESENT = 0x00000002,
|
|
|
|
|
DIGCF_ALLCLASSES = 0x00000004,
|
|
|
|
|
DIGCF_PROFILE = 0x00000008,
|
|
|
|
|
DIGCF_DEVICEINTERFACE = 0x00000010,
|
|
|
|
|
}
|
|
|
|
|
}
|