using XFESystem.NativeKernel;
namespace XFESystem.Platform.Tests;
public unsafe class UsbHidTests
{
static readonly byte[] Mouse = [0x05,1,0x09,2,0xa1,1,0x09,1,0xa1,0,0x05,9,0x19,1,0x29,5,
0x15,0,0x25,1,0x95,5,0x75,1,0x81,2,0x95,1,0x75,3,0x81,1,0x05,1,0x09,0x30,0x09,0x31,0x09,0x38,
0x15,0x81,0x25,0x7f,0x75,8,0x95,3,0x81,6,0xc0,0xc0];
[Fact] public void BootKeyboardProducesTransitionsAndUnicodeWithoutRepeatingSnapshot()
{
UsbHidField* fields=stackalloc UsbHidField[512];var layout=new UsbHidLayout{Fields=fields,Capacity=512};
UsbHid.BootKeyboard(&layout);UsbHidState state=default;NativeInputReport* output=stackalloc NativeInputReport[64];
byte* report=stackalloc byte[8];for(int i=0;i<8;i++)report[i]=0;report[2]=4;
Assert.Equal(1u,UsbHid.Decode(&layout,&state,report,8,output,64));
Assert.Equal(Ps2Decoder.KeyDown,output[0].Kind);Assert.Equal((uint)'a',output[0].Unicode);
Assert.Equal(0u,UsbHid.Decode(&layout,&state,report,8,output,64));
report[2]=0;Assert.Equal(1u,UsbHid.Decode(&layout,&state,report,8,output,64));Assert.Equal(Ps2Decoder.KeyUp,output[0].Kind);
}
[Theory][InlineData(1,0x1d,Ps2Decoder.Control)][InlineData(2,0x2a,Ps2Decoder.Shift)]
[InlineData(4,0x38,Ps2Decoder.Alt)][InlineData(8,0x15b,Ps2Decoder.Super)]
[InlineData(16,0x11d,Ps2Decoder.Control)][InlineData(32,0x36,Ps2Decoder.Shift)]
[InlineData(64,0x138,Ps2Decoder.Alt)][InlineData(128,0x15c,Ps2Decoder.Super)]
public void BothSidedModifiersUseTheExistingDesktopAbi(byte modifier,uint key,uint expected)
{
UsbHidField* fields=stackalloc UsbHidField[512];var layout=new UsbHidLayout{Fields=fields,Capacity=512};
UsbHid.BootKeyboard(&layout);UsbHidState state=default;NativeInputReport* output=stackalloc NativeInputReport[64];
byte* report=stackalloc byte[8];for(int i=0;i<8;i++)report[i]=0;report[0]=modifier;
Assert.Equal(1u,UsbHid.Decode(&layout,&state,report,8,output,64));Assert.Equal(key,output[0].KeyCode);Assert.Equal(expected,output[0].Modifiers);
report[0]=0;Assert.Equal(1u,UsbHid.Decode(&layout,&state,report,8,output,64));Assert.Equal(0u,output[0].Modifiers);
}
[Fact] public void RelativeMouseHandlesButtonsSignedMotionAndWheel()
{
UsbHidField* fields=stackalloc UsbHidField[512];var layout=new UsbHidLayout{Fields=fields,Capacity=512};
fixed(byte* descriptor=Mouse)Assert.True(UsbHid.Parse(descriptor,(uint)Mouse.Length,&layout));
Assert.Equal(2u,layout.Kind);UsbHidState state=default;NativeInputReport* output=stackalloc NativeInputReport[64];
byte* report=stackalloc byte[4];report[0]=5;report[1]=254;report[2]=3;report[3]=1;
Assert.Equal(1u,UsbHid.Decode(&layout,&state,report,4,output,64));
Assert.Equal(Ps2Decoder.Pointer,output[0].Kind);Assert.Equal(-2,output[0].DeltaX);Assert.Equal(3,output[0].DeltaY);
Assert.Equal(1,output[0].Wheel);Assert.Equal(5u,output[0].Buttons);
Assert.Equal(0u,UsbHid.Decode(&layout,&state,report,3,output,64));
}
[Fact] public void CompositeReportIdsDoNotInterpretVendorReportsAsKeyboards()
{
byte[] descriptor=[0x05,7,0x85,1,0x19,4,0x29,11,0x75,1,0x95,8,0x81,2,
0x06,0,0xff,0x85,2,0x75,8,0x95,1,0x81,2];
UsbHidField* fields=stackalloc UsbHidField[512];var layout=new UsbHidLayout{Fields=fields,Capacity=512};
fixed(byte* p=descriptor)Assert.True(UsbHid.Parse(p,(uint)descriptor.Length,&layout));
UsbHidState state=default;NativeInputReport* output=stackalloc NativeInputReport[64];byte* data=stackalloc byte[2];data[0]=2;data[1]=255;
Assert.Equal(0u,UsbHid.Decode(&layout,&state,data,2,output,64));data[0]=1;data[1]=1;
Assert.Equal(1u,UsbHid.Decode(&layout,&state,data,2,output,64));Assert.Equal((uint)'a',output[0].Unicode);
}
[Fact] public void TruncationAndRolloverPreserveTheHeldKeysUntilValidRelease()
{
UsbHidField* fields=stackalloc UsbHidField[512];var layout=new UsbHidLayout{Fields=fields,Capacity=512};UsbHid.BootKeyboard(&layout);
UsbHidState state=default;NativeInputReport* output=stackalloc NativeInputReport[64];byte* data=stackalloc byte[8];for(int i=0;i<8;i++)data[i]=0;data[2]=4;
Assert.Equal(1u,UsbHid.Decode(&layout,&state,data,8,output,64));data[2]=1;
Assert.Equal(0u,UsbHid.Decode(&layout,&state,data,8,output,64));data[2]=0;
Assert.Equal(0u,UsbHid.Decode(&layout,&state,data,2,output,64));
Assert.Equal(1u,UsbHid.Decode(&layout,&state,data,8,output,64));Assert.Equal(Ps2Decoder.KeyUp,output[0].Kind);
}
[Theory][InlineData(new byte[]{0xfe,0,0})][InlineData(new byte[]{0x75})]
[InlineData(new byte[]{0x75,33,0x95,1,0x81,2})][InlineData(new byte[]{0xb4})]
[InlineData(new byte[]{0x85,0})][InlineData(new byte[]{0x96,1,1})]
public void MalformedOrUnboundedDescriptorsAreRejected(byte[] descriptor)
{
UsbHidField* fields=stackalloc UsbHidField[512];var layout=new UsbHidLayout{Fields=fields,Capacity=512};
fixed(byte* p=descriptor)Assert.False(UsbHid.Parse(p,(uint)descriptor.Length,&layout));
}
[Fact] public void ExhaustingFieldsIsRejectedWithinTheCallerStorage()
{
UsbHidField* fields=stackalloc UsbHidField[2];fields[1].Flags=0xdeadbeef;var layout=new UsbHidLayout{Fields=fields,Capacity=1};
fixed(byte* p=Mouse)Assert.False(UsbHid.Parse(p,(uint)Mouse.Length,&layout));Assert.Equal(0xdeadbeefu,fields[1].Flags);
}
}
using XFESystem.NativeKernel;
namespace XFESystem.Platform.Tests;
public unsafe class UsbHidTests
{
static readonly byte[] Mouse = [0x05,1,0x09,2,0xa1,1,0x09,1,0xa1,0,0x05,9,0x19,1,0x29,5,
0x15,0,0x25,1,0x95,5,0x75,1,0x81,2,0x95,1,0x75,3,0x81,1,0x05,1,0x09,0x30,0x09,0x31,0x09,0x38,
0x15,0x81,0x25,0x7f,0x75,8,0x95,3,0x81,6,0xc0,0xc0];
[Fact] public void BootKeyboardProducesTransitionsAndUnicodeWithoutRepeatingSnapshot()
{
UsbHidField* fields=stackalloc UsbHidField[512];var layout=new UsbHidLayout{Fields=fields,Capacity=512};
UsbHid.BootKeyboard(&layout);UsbHidState state=default;NativeInputReport* output=stackalloc NativeInputReport[64];
byte* report=stackalloc byte[8];for(int i=0;i<8;i++)report[i]=0;report[2]=4;
Assert.Equal(1u,UsbHid.Decode(&layout,&state,report,8,output,64));
Assert.Equal(Ps2Decoder.KeyDown,output[0].Kind);Assert.Equal((uint)'a',output[0].Unicode);
Assert.Equal(0u,UsbHid.Decode(&layout,&state,report,8,output,64));
report[2]=0;Assert.Equal(1u,UsbHid.Decode(&layout,&state,report,8,output,64));Assert.Equal(Ps2Decoder.KeyUp,output[0].Kind);
}
[Theory][InlineData(1,0x1d,Ps2Decoder.Control)][InlineData(2,0x2a,Ps2Decoder.Shift)]
[InlineData(4,0x38,Ps2Decoder.Alt)][InlineData(8,0x15b,Ps2Decoder.Super)]
[InlineData(16,0x11d,Ps2Decoder.Control)][InlineData(32,0x36,Ps2Decoder.Shift)]
[InlineData(64,0x138,Ps2Decoder.Alt)][InlineData(128,0x15c,Ps2Decoder.Super)]
public void BothSidedModifiersUseTheExistingDesktopAbi(byte modifier,uint key,uint expected)
{
UsbHidField* fields=stackalloc UsbHidField[512];var layout=new UsbHidLayout{Fields=fields,Capacity=512};
UsbHid.BootKeyboard(&layout);UsbHidState state=default;NativeInputReport* output=stackalloc NativeInputReport[64];
byte* report=stackalloc byte[8];for(int i=0;i<8;i++)report[i]=0;report[0]=modifier;
Assert.Equal(1u,UsbHid.Decode(&layout,&state,report,8,output,64));Assert.Equal(key,output[0].KeyCode);Assert.Equal(expected,output[0].Modifiers);
report[0]=0;Assert.Equal(1u,UsbHid.Decode(&layout,&state,report,8,output,64));Assert.Equal(0u,output[0].Modifiers);
}
[Fact] public void RelativeMouseHandlesButtonsSignedMotionAndWheel()
{
UsbHidField* fields=stackalloc UsbHidField[512];var layout=new UsbHidLayout{Fields=fields,Capacity=512};
fixed(byte* descriptor=Mouse)Assert.True(UsbHid.Parse(descriptor,(uint)Mouse.Length,&layout));
Assert.Equal(2u,layout.Kind);UsbHidState state=default;NativeInputReport* output=stackalloc NativeInputReport[64];
byte* report=stackalloc byte[4];report[0]=5;report[1]=254;report[2]=3;report[3]=1;
Assert.Equal(1u,UsbHid.Decode(&layout,&state,report,4,output,64));
Assert.Equal(Ps2Decoder.Pointer,output[0].Kind);Assert.Equal(-2,output[0].DeltaX);Assert.Equal(3,output[0].DeltaY);
Assert.Equal(1,output[0].Wheel);Assert.Equal(5u,output[0].Buttons);
Assert.Equal(0u,UsbHid.Decode(&layout,&state,report,3,output,64));
}
[Fact] public void CompositeReportIdsDoNotInterpretVendorReportsAsKeyboards()
{
byte[] descriptor=[0x05,7,0x85,1,0x19,4,0x29,11,0x75,1,0x95,8,0x81,2,
0x06,0,0xff,0x85,2,0x75,8,0x95,1,0x81,2];
UsbHidField* fields=stackalloc UsbHidField[512];var layout=new UsbHidLayout{Fields=fields,Capacity=512};
fixed(byte* p=descriptor)Assert.True(UsbHid.Parse(p,(uint)descriptor.Length,&layout));
UsbHidState state=default;NativeInputReport* output=stackalloc NativeInputReport[64];byte* data=stackalloc byte[2];data[0]=2;data[1]=255;
Assert.Equal(0u,UsbHid.Decode(&layout,&state,data,2,output,64));data[0]=1;data[1]=1;
Assert.Equal(1u,UsbHid.Decode(&layout,&state,data,2,output,64));Assert.Equal((uint)'a',output[0].Unicode);
}
[Fact] public void TruncationAndRolloverPreserveTheHeldKeysUntilValidRelease()
{
UsbHidField* fields=stackalloc UsbHidField[512];var layout=new UsbHidLayout{Fields=fields,Capacity=512};UsbHid.BootKeyboard(&layout);
UsbHidState state=default;NativeInputReport* output=stackalloc NativeInputReport[64];byte* data=stackalloc byte[8];for(int i=0;i<8;i++)data[i]=0;data[2]=4;
Assert.Equal(1u,UsbHid.Decode(&layout,&state,data,8,output,64));data[2]=1;
Assert.Equal(0u,UsbHid.Decode(&layout,&state,data,8,output,64));data[2]=0;
Assert.Equal(0u,UsbHid.Decode(&layout,&state,data,2,output,64));
Assert.Equal(1u,UsbHid.Decode(&layout,&state,data,8,output,64));Assert.Equal(Ps2Decoder.KeyUp,output[0].Kind);
}
[Theory][InlineData(new byte[]{0xfe,0,0})][InlineData(new byte[]{0x75})]
[InlineData(new byte[]{0x75,33,0x95,1,0x81,2})][InlineData(new byte[]{0xb4})]
[InlineData(new byte[]{0x85,0})][InlineData(new byte[]{0x96,1,1})]
public void MalformedOrUnboundedDescriptorsAreRejected(byte[] descriptor)
{
UsbHidField* fields=stackalloc UsbHidField[512];var layout=new UsbHidLayout{Fields=fields,Capacity=512};
fixed(byte* p=descriptor)Assert.False(UsbHid.Parse(p,(uint)descriptor.Length,&layout));
}
[Fact] public void ExhaustingFieldsIsRejectedWithinTheCallerStorage()
{
UsbHidField* fields=stackalloc UsbHidField[2];fields[1].Flags=0xdeadbeef;var layout=new UsbHidLayout{Fields=fields,Capacity=1};
fixed(byte* p=Mouse)Assert.False(UsbHid.Parse(p,(uint)Mouse.Length,&layout));Assert.Equal(0xdeadbeefu,fields[1].Flags);
}
}