Read and patch LEB128 values
Advanced · C#. LEB128 has a runtime storage width. Decoded counts can size arrays; updates must preserve the encoded extent.
Run this example
Prerequisites: the repository's .NET 10 SDK and a checkout of this source. Run from the repository root:
dotnet run --project docs/examples/CStructSharp.Docs.Examples.csproj -c Release -- variable-integers
The runner checks 127 and 128 in different byte widths, signed -65, and a same-width update to 129. Success includes PASS variable-integers.
Try the related browser lesson.
Complete program
The layout, options, input bytes, helper methods, and required types are all included. To adapt it outside the repository, create a .NET 10 console project, add CStructSharp, and replace Program.cs with this complete file. These examples follow the source version; use a matching package when testing a release.
Download the complete C# source.
// Generated from executable documentation examples. Edit the source region, then regenerate.
using System;
using System.IO;
using System.IO.Pipelines;
using System.Linq;
using System.Buffers;
using System.Collections.Generic;
using System.Dynamic;
using System.Globalization;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using CStructSharp;
using CStructSharp.Codecs;
using CStructSharp.Diagnostics;
using CStructSharp.Introspection;
using CStructSharp.Values;
internal static partial class Program
{
public static void Main()
{
VariableIntegers();
Console.WriteLine("PASS variable-integers");
}
private static void VariableIntegers()
{
var layout = new CStruct("struct root { uleb128_32 count; uleb128_64 values[count]; sleb128_32 delta; uint8 tail; };", aligned: false);
byte[] bytes = layout.Serialize("root", new Dictionary<string, object?> { ["count"] = 2, ["values"] = new ulong[] { 127, 128 }, ["delta"] = -65, ["tail"] = 99 });
SequenceEqual([2, 127, 128, 1, 191, 127, 99], bytes);
using var stream = new MemoryStream(bytes);
Equal(2, layout.GetArrayLength(stream, "root.values"));
Equal(4L, layout.ResolveAddress(stream, "root.delta"));
Equal(-65, layout.ReadValue<int>(stream, "root.delta"));
stream.Position = 0;
layout.Update(stream, "root.values[1]", 129UL);
SequenceEqual([2, 127, 129, 1, 191, 127, 99], stream.ToArray());
Throws<CStructWriteException>(() => layout.Update(stream, "root.values[1]", 1UL));
SequenceEqual([2, 127, 129, 1, 191, 127, 99], stream.ToArray());
}
private static void Equal<T>(T expected, T actual)
{
if (!EqualityComparer<T>.Default.Equals(expected, actual))
{
throw new InvalidOperationException($"Expected '{expected}', received '{actual}'.");
}
}
private static void SequenceEqual(byte[] expected, byte[] actual)
{
if (!expected.AsSpan().SequenceEqual(actual))
{
throw new InvalidOperationException(
$"Expected {Convert.ToHexString(expected)}, received {Convert.ToHexString(actual)}.");
}
}
private static void Throws<TException>(Action action)
where TException : Exception
{
try
{
action();
}
catch (TException)
{
return;
}
throw new InvalidOperationException($"Expected {typeof(TException).Name}.");
}
}
Try it and diagnose mistakes
Replace 128 with 1 through Update.
Answer: The replacement needs one byte instead of two, so the update fails and leaves the buffer unchanged. The program contains assertions for its original inputs. When changing an input intentionally, update the expected assertion too; an unchanged assertion is not evidence that the new value is wrong.
Continue with the related guide or choose another recipe.