Skip to content

Commit

Permalink
Fix DynamicStruct
Browse files Browse the repository at this point in the history
  • Loading branch information
ThadHouse committed Jan 19, 2024
1 parent 32e80da commit 9d15c84
Show file tree
Hide file tree
Showing 2 changed files with 214 additions and 4 deletions.
63 changes: 59 additions & 4 deletions src/wpiutil/Serialization/Struct/DynamicStruct.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,65 @@ public string GetStringField(StructFieldDescriptor field)
}

ReadOnlySpan<byte> bytes = Buffer.Span[field.Offset..field.ArraySize];
return Encoding.UTF8.GetString(bytes);

// Find last non zero character
int stringLength = bytes.Length;
for (; stringLength > 0; stringLength--)
{
if (bytes[stringLength - 1] != 0)
{
break;
}
}
// If string is all zeroes, its empty and return an empty string.
if (stringLength == 0)
{
return "";
}
// Check if the end of the string is in the middle of a continuation byte or not.
if ((bytes[stringLength - 1] & 0x80) != 0)
{
// This is a UTF8 continuation byte. Make sure its valid.
// Walk back until initial byte is found
int utf8StartByte = stringLength;
for (; utf8StartByte > 0; utf8StartByte--)
{
if ((bytes[utf8StartByte - 1] & 0x40) != 0)
{
// Having 2nd bit set means start byte
break;
}
}
if (utf8StartByte == 0)
{
// This case means string only contains continuation bytes
return "";
}
utf8StartByte--;
// Check if its a 2, 3, or 4 byte
byte checkByte = bytes[utf8StartByte];
if ((checkByte & 0xE0) == 0xC0 && utf8StartByte != stringLength - 2)
{
// 2 byte, need 1 more byte
stringLength = utf8StartByte;
}
else if ((checkByte & 0xF0) == 0xE0 && utf8StartByte != stringLength - 3)
{
// 3 byte, need 2 more bytes
stringLength = utf8StartByte;
}
else if ((checkByte & 0xF8) == 0xF0 && utf8StartByte != stringLength - 4)
{
// 4 byte, need 3 more bytes
stringLength = utf8StartByte;
}
// If we get here, the string is either completely garbage or fine.
}

return Encoding.UTF8.GetString(bytes[..stringLength]);
}

public void SetStringField(StructFieldDescriptor field, string value)
public bool SetStringField(StructFieldDescriptor field, string value)
{
if (field.Type.Type != StructFieldType.Char)
{
Expand All @@ -147,9 +202,9 @@ public void SetStringField(StructFieldDescriptor field, string value)
}

Span<byte> bytes = Buffer.Span[field.Offset..field.ArraySize];
Encoding.UTF8.GetEncoder().Convert(value, bytes, false, out int _, out int bytesUsed, out bool _);
Encoding.UTF8.GetEncoder().Convert(value, bytes, false, out int _, out int bytesUsed, out bool complete);
bytes[bytesUsed..].Clear();

return complete;
}

public DynamicStruct GetStructField(StructFieldDescriptor field, int arrIndex = 0)
Expand Down
155 changes: 155 additions & 0 deletions test/wpiutil.test/DynamicStructTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,159 @@ public void TestNestedStruct()
Assert.True(desc2.IsValid);
Assert.Equal(4, desc2.Size);
}

[Fact]
public void TestStringAllZeros()
{
var db = new StructDescriptorDatabase();
var desc = db.Add("test", "char a[32]");
var dynamic = DynamicStruct.Allocate(desc);
var field = desc.FindFieldByName("a");
Assert.NotNull(field);
Assert.Equal("", dynamic.GetStringField(field));
}

[Fact]
public void TestStringRoundTrip()
{
var db = new StructDescriptorDatabase();
var desc = db.Add("test", "char a[32]");
var dynamic = DynamicStruct.Allocate(desc);
var field = desc.FindFieldByName("a");
Assert.NotNull(field);
Assert.True(dynamic.SetStringField(field, "abc"));
Assert.Equal("abc", dynamic.GetStringField(field));
}

[Fact]
public void TestStringRoundTripEmbeddedNull()
{
var db = new StructDescriptorDatabase();
var desc = db.Add("test", "char a[32]");
var dynamic = DynamicStruct.Allocate(desc);
var field = desc.FindFieldByName("a");
Assert.NotNull(field);
Assert.True(dynamic.SetStringField(field, "ab\0c"));
Assert.Equal("ab\0c", dynamic.GetStringField(field));
}

[Fact]
public void TestStringRoundTripStringTooLong()
{
var db = new StructDescriptorDatabase();
var desc = db.Add("test", "char a[2]");
var dynamic = DynamicStruct.Allocate(desc);
var field = desc.FindFieldByName("a");
Assert.NotNull(field);
Assert.False(dynamic.SetStringField(field, "abc"));
Assert.Equal("ab", dynamic.GetStringField(field));
}

[Fact]
public void TestStringRoundTripPartial2ByteUtf8()
{
var db = new StructDescriptorDatabase();
var desc = db.Add("test", "char a[2]");
var dynamic = DynamicStruct.Allocate(desc);
var field = desc.FindFieldByName("a");
Assert.NotNull(field);
Assert.False(dynamic.SetStringField(field, "a\u0234"));
Assert.Equal("a", dynamic.GetStringField(field));
}

[Fact]
public void TestStringRoundTrip2ByteUtf8()
{
var db = new StructDescriptorDatabase();
var desc = db.Add("test", "char a[3]");
var dynamic = DynamicStruct.Allocate(desc);
var field = desc.FindFieldByName("a");
Assert.NotNull(field);
Assert.True(dynamic.SetStringField(field, "a\u0234"));
Assert.Equal("a\u0234", dynamic.GetStringField(field));
}

[Fact]
public void TestStringRoundTripPartial3ByteUtf8FirstByte()
{
var db = new StructDescriptorDatabase();
var desc = db.Add("test", "char a[2]");
var dynamic = DynamicStruct.Allocate(desc);
var field = desc.FindFieldByName("a");
Assert.NotNull(field);
Assert.False(dynamic.SetStringField(field, "a\u1234"));
Assert.Equal("a", dynamic.GetStringField(field));
}

[Fact]
public void TestStringRoundTripPartial3ByteUtf8SecondByte()
{
var db = new StructDescriptorDatabase();
var desc = db.Add("test", "char a[3]");
var dynamic = DynamicStruct.Allocate(desc);
var field = desc.FindFieldByName("a");
Assert.NotNull(field);
Assert.False(dynamic.SetStringField(field, "a\u1234"));
Assert.Equal("a", dynamic.GetStringField(field));
}

[Fact]
public void TestStringRoundTrip3ByteUtf8()
{
var db = new StructDescriptorDatabase();
var desc = db.Add("test", "char a[4]");
var dynamic = DynamicStruct.Allocate(desc);
var field = desc.FindFieldByName("a");
Assert.NotNull(field);
Assert.True(dynamic.SetStringField(field, "a\u1234"));
Assert.Equal("a\u1234", dynamic.GetStringField(field));
}

[Fact]
public void TestStringRoundTripPartial4ByteUtf8FirstByte()
{
var db = new StructDescriptorDatabase();
var desc = db.Add("test", "char a[2]");
var dynamic = DynamicStruct.Allocate(desc);
var field = desc.FindFieldByName("a");
Assert.NotNull(field);
Assert.False(dynamic.SetStringField(field, "a\uD83D\uDC00"));
Assert.Equal("a", dynamic.GetStringField(field));
}

[Fact]
public void TestStringRoundTripPartial4ByteUtf8SecondByte()
{
var db = new StructDescriptorDatabase();
var desc = db.Add("test", "char a[3]");
var dynamic = DynamicStruct.Allocate(desc);
var field = desc.FindFieldByName("a");
Assert.NotNull(field);
Assert.False(dynamic.SetStringField(field, "a\uD83D\uDC00"));
Assert.Equal("a", dynamic.GetStringField(field));
}

[Fact]
public void TestStringRoundTripPartial4ByteUtf8ThirdByte()
{
var db = new StructDescriptorDatabase();
var desc = db.Add("test", "char a[4]");
var dynamic = DynamicStruct.Allocate(desc);
var field = desc.FindFieldByName("a");
Assert.NotNull(field);
Assert.False(dynamic.SetStringField(field, "a\uD83D\uDC00"));
Assert.Equal("a", dynamic.GetStringField(field));
}

[Fact]
public void TestStringRoundTrip4ByteUtf8()
{
var db = new StructDescriptorDatabase();
var desc = db.Add("test", "char a[5]");
var dynamic = DynamicStruct.Allocate(desc);
var field = desc.FindFieldByName("a");
Assert.NotNull(field);
Assert.True(dynamic.SetStringField(field, "a\uD83D\uDC00"));
Assert.Equal("a\uD83D\uDC00", dynamic.GetStringField(field));
}
}

0 comments on commit 9d15c84

Please sign in to comment.