Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,13 @@ flowchart TD
- TerminalLayer
- Crc8Layer (0xA6)
- Crc16Layer (0xBAAD)
- Crc32Layer (reversed 0x04c11db7)
- LoggingLayer using ILogger
- LoopbackLayer
- BufferLayer
- SegmentationLayer
- ArqLayer
- IdleCheckLayer

### Transport layers
- ZeroMQ using [NetMQ](https://netmq.readthedocs.io/)
Expand Down Expand Up @@ -170,12 +172,15 @@ python -m libstored.gui -p 5555

## Compatibility

Tested with:
- [v1.7.1](https://github.com/DEMCON/libstored/releases/tag/v1.7.1) (see note below)
Tested with libstored versions:
- [v2.0.0](https://github.com/DEMCON/libstored/releases/tag/v2.0.0)
- [v1.8.0](https://github.com/DEMCON/libstored/releases/tag/v1.8.0)
- [v1.7.1](https://github.com/DEMCON/libstored/releases/tag/v1.7.1) (see note below)

See [libstored changelog](https://demcon.github.io/libstored/doc/changelog.html) for the changes. There do not seem to be breaking changes from <v1.7.1 to v1.7.1 for this C# implementation.

Only the latest major version of libstored will be supported.

### libstored >v1.7.1
The ZeroMQ socket type changed from `PAIR` to `DEALER` for the SyncZeroMQLayer. Make sure the exact same socket type is used at both ends of the protocol, so `PAIR` - `PAIR` or `DEALER` - `DEALER`, but not `PAIR` - `DEALER`. When connecting to libstored v1.7.1 or lower, use the `PAIR` socket type. For version v1.8 or higher of libstored, use `DEALER`.

Expand Down
7 changes: 5 additions & 2 deletions examples/Arq/LibStored.Arq/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# Add ARG for libstored version
ARG LIBSTORED_VERSION=1.8.0
ARG LIBSTORED_VERSION=2.0.0

FROM ubuntu:22.04 as builder

# Use the ARG after FROM
ARG LIBSTORED_VERSION

RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections

RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
Expand All @@ -19,6 +21,7 @@ RUN apt-get update && \
plantuml \
python3-venv \
python3-dev \
python3-tk \
lsb-release \
libgl1 \
libegl1 \
Expand Down Expand Up @@ -62,4 +65,4 @@ COPY --from=builder /app /app

WORKDIR /app/bin

CMD ["./arq"]
CMD ["./arq"]
2 changes: 1 addition & 1 deletion examples/Arq/LibStored.Net.Arq.AppHost/AppHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

var builder = DistributedApplication.CreateBuilder(args);

const string libstoredVersion = "1.8.0";
const string libstoredVersion = "2.0.0";
var arq = builder.AddDockerfile("libstored-arq", "../LibStored.Arq/", "Dockerfile")
.WithImage("demcon/libstored-arq-example")
.WithImageTag(libstoredVersion)
Expand Down
8 changes: 8 additions & 0 deletions examples/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project>
<Import Project="..\Directory.Build.props" />
<PropertyGroup>
<!-- Only use a single framework for the examples -->
<TargetFrameworks>net10.0</TargetFrameworks>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
</Project>
5 changes: 4 additions & 1 deletion examples/Sync/LibStored.Net.Example.AppHost/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# Add ARG for libstored version
ARG LIBSTORED_VERSION=1.8.0
ARG LIBSTORED_VERSION=2.0.0

FROM ubuntu:22.04 as builder

# Use the ARG after FROM
ARG LIBSTORED_VERSION

RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections

RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
Expand All @@ -19,6 +21,7 @@ RUN apt-get update && \
plantuml \
python3-venv \
python3-dev \
python3-tk \
lsb-release \
libgl1 \
libegl1 \
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<Sdk Name="Aspire.AppHost.Sdk" Version="13.0.0" />
<Sdk Name="Aspire.AppHost.Sdk" Version="13.0.1" />

<PropertyGroup>
<OutputType>Exe</OutputType>
Expand All @@ -10,9 +10,9 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.0.0" />
<PackageReference Include="Aspire.Hosting.Docker" Version="13.0.0-preview.1.25560.3" />
<PackageReference Include="Aspire.Hosting.Python" Version="13.0.0" />
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.0.1" />
<PackageReference Include="Aspire.Hosting.Docker" Version="13.0.1-preview.1.25575.3" />
<PackageReference Include="Aspire.Hosting.Python" Version="13.0.1" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion examples/Sync/LibStored.Net.Example.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

builder.AddDockerComposeEnvironment("docker-compose");

const string libstoredVersion = "1.8.0";
const string libstoredVersion = "2.0.0";
var sync = builder.AddDockerfile("libstored", ".", "Dockerfile")
.WithImage("demcon/libstored")
.WithImageTag(libstoredVersion)
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
libstored==1.7.1
libstored==2.0.0
21 changes: 21 additions & 0 deletions examples/Sync/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Sync Example

This example demonstrates cross-language synchronization between C++ and C# using the [Aspire CLI](https://aspire.dev/docs/cli/). The setup is based on the [8_sync](https://github.com/DEMCON/libstored/tree/main/examples/8_sync) example from the libstored project.

In this example, the C++ code runs inside a container, while the C# implementation runs natively. The `libstored.gui` Python library connects to both the C++ and C# components, allowing you to observe and interact with the synchronizated stores.

## Prerequisites

- [.NET 10 SDK](https://dotnet.microsoft.com/download)
- [Aspire CLI](https://aspire.dev/docs/cli/)
- [Podman](https://podman.io/) or [Docker](https://www.docker.com/products/docker-desktop)

## Running the Example

```sh
aspire run
```

## Learn More

- [Aspire Documentation](https://aspire.dev/docs/)
2 changes: 1 addition & 1 deletion src/LibStored.Net/Protocol/Crc16Layer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public override void Decode(Span<byte> buffer)
crc = Compute(buffer[i], crc);
}

if (crc != (ushort)(buffer[buffer.Length - 2] << 8 | buffer[buffer.Length - 1]))
if (crc != BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(buffer.Length - 2, 2)))
{
// CRC mismatch, ignore the packet
_logger?.LogWarning("Invalid crc check: {Actual}, expected: {Expected}", buffer[^1], crc);
Expand Down
140 changes: 140 additions & 0 deletions src/LibStored.Net/Protocol/Crc32Layer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// SPDX-FileCopyrightText: 2025 Guus Kuiper
//
// SPDX-License-Identifier: MIT

using System.Buffers.Binary;
using Microsoft.Extensions.Logging;

namespace LibStored.Net.Protocol;

/// <summary>
/// A protocol layer that adds a 32-bit CRC (reversed polynomial 0x04c11db7) for message integrity.
/// Calculates and verifies CRC values for encoded and decoded messages.
/// </summary>
public class Crc32Layer : ProtocolLayer
{
private static readonly uint[] crc32Table =
[
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535,
0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD,
0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D,
0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4,
0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C,
0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC,
0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB,
0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F,
0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB,
0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA,
0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE,
0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A,
0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409,
0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,
0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739,
0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268,
0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0,
0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8,
0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF,
0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703,
0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7,
0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE,
0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6,
0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D,
0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5,
0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605,
0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D,
];

private readonly ILogger<Crc32Layer>? _logger;
private readonly uint _init = 0xffffffff;
private readonly uint _finalXor = 0xffffffff;
private uint _crc;

/// <summary>
/// Initializes a new instance of the <see cref="Crc32Layer"/> class.
/// </summary>
/// <param name="logger">Optional logger for CRC warnings.</param>
public Crc32Layer(ILogger<Crc32Layer>? logger = null)
{
_logger = logger;
_crc = _init; // Initialize CRC to the initial value
}

/// <summary>
/// Decodes a buffer, verifying the CRC and passing valid data to the next layer.
/// </summary>
/// <param name="buffer">The buffer to decode, including CRC bytes.</param>
public override void Decode(Span<byte> buffer)
{
if (buffer.Length < 4)
{
return;
}

uint crc = _init;
for (int i = 0; i < buffer.Length - 4; i++)
{
crc = Compute(buffer[i], crc);
}

uint bufferCrc = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(buffer.Length - 4, 4));
if ((crc ^ _finalXor) != bufferCrc)
{
// CRC mismatch, ignore the packet
_logger?.LogWarning("Invalid crc check: {Actual}, expected: {Expected}", bufferCrc, crc);
return;
}

base.Decode(buffer.Slice(0, buffer.Length - 4));
}

/// <summary>
/// Encodes a buffer, updating the CRC and appending it when the message is complete.
/// </summary>
/// <param name="buffer">The data to encode.</param>
/// <param name="last">Indicates if this is the last buffer in the message.</param>
public override void Encode(ReadOnlySpan<byte> buffer, bool last)
{
foreach (byte b in buffer)
{
_crc = Compute(b, _crc);
}

base.Encode(buffer, false);

if (last)
{
uint crc = _crc ^ _finalXor;
byte[] crcBytesBig = new byte[4];
BinaryPrimitives.WriteUInt32BigEndian(crcBytesBig, crc);
base.Encode(crcBytesBig, true);
_crc = _init; // Reset CRC for the next message
}
}

/// <inheritdoc />
public override void Reset()
{
_crc = _init;
base.Reset();
}

/// <inheritdoc />
public override int Mtu() => base.Mtu() switch
{
0 => 0,
<= 5 => 1,
var x => x - 4,
};

private uint Compute(byte input, uint crc) => crc32Table[input ^ (byte)(crc & 0xFF)] ^ (crc >> 8);
}
52 changes: 52 additions & 0 deletions src/LibStored.Net/Protocol/IdleCheckLayer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: 2025 Guus Kuiper
//
// SPDX-License-Identifier: MIT

namespace LibStored.Net.Protocol;

/// <summary>
/// A layer that tracks if it sees communication through the stack.
///
/// This may be used to check of long inactivity on stalled or disconnected
/// communication channels.
/// </summary>
public class IdleCheckLayer : ProtocolLayer
{
/// <summary>
/// Checks if upstream was idle since the last call to <see cref="SetIdle"/>
/// </summary>
public bool IdleUp { get; private set; } = true;

/// <summary>
/// Checks if downstream was idle since the last call to <see cref="SetIdle"/>
/// </summary>
public bool IdleDown { get; private set; } = true;

/// <summary>
/// Checks if both up and down the stack was idle since the last call to <see cref="SetIdle"/>.
/// </summary>
public bool Idle => IdleUp && IdleDown;

/// <summary>
/// Resets the idle flags.
/// </summary>
public void SetIdle()
{
IdleUp = true;
IdleDown = true;
}

/// <inheritdoc />
public override void Decode(Span<byte> buffer)
{
IdleUp = false;
base.Decode(buffer);
}

/// <inheritdoc />
public override void Encode(ReadOnlySpan<byte> buffer, bool last)
{
IdleDown = false;
base.Encode(buffer, last);
}
}
Loading
Loading