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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,6 @@ Thumbs.db

## Claude Code (per-machine settings, transcripts, etc.)
.claude/

## Visual Studio extension cache (per-user, e.g. CodeRush / similar)
.cr/
6 changes: 5 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AnalysisLevel>latest-recommended</AnalysisLevel>
<!-- TODO: tighten back to 'latest-recommended' before Phase 5 / first NuGet release.
Relaxed to 'latest-default' during MVP because opinionated rules
(CA1848 LoggerMessage, CA1711 naming, etc.) were blocking the
walking-skeleton workflow with no real value at this stage. -->
<AnalysisLevel>latest-default</AnalysisLevel>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
</PropertyGroup>

Expand Down
16 changes: 5 additions & 11 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>

<ItemGroup Label="Runtime">
<!-- 主 lib 不依賴外部套件,這裡只列 DI 抽象介面(屬於 Microsoft.Extensions 家族,
算 BCL 延伸;如未來真的不想吃任何 NuGet,可改用 ServiceCollection 自己包) -->
Expand All @@ -13,24 +12,19 @@
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
</ItemGroup>

<ItemGroup Label="Sample">
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
</ItemGroup>

<ItemGroup Label="Test">
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageVersion Include="xunit.v3" Version="1.0.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.0" />
<PackageVersion Include="FluentAssertions" Version="7.0.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.5.1" />
<PackageVersion Include="xunit.v3" Version="3.2.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
<PackageVersion Include="coverlet.collector" Version="10.0.0" />
</ItemGroup>

<ItemGroup Label="IntegrationTest">
<PackageVersion Include="Testcontainers" Version="4.0.0" />
</ItemGroup>

<ItemGroup Label="Build">
<PackageVersion Include="MinVer" Version="6.0.0" />
</ItemGroup>
</Project>
</Project>
30 changes: 30 additions & 0 deletions NuGet.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Repo-level NuGet configuration.

Why this file exists:
Directory.Packages.props enables Central Package Management
(ManagePackageVersionsCentrally=true). When CPM is on, NuGet refuses to
restore if more than one package source is defined unless package
source mapping is configured (NU1507). Some developer machines have
extra sources (e.g. a corporate Azure Artifacts feed) configured at
user / machine level, which triggers NU1507.

What it does:
<clear/> wipes any sources inherited from the user-level or
machine-level NuGet config, then we register only the public
nuget.org feed. Result: builds are deterministic and identical on any
machine — no dependence on the developer's user-level config.

Intranet / air-gapped builds:
The CLAUDE.md dual-network workflow expects intranet-only builds to
live on the 'ci/offline' branch (which never reaches origin). That
branch should override this file with whatever mirror / private feed
the intranet uses. Do NOT add a private feed here on origin.
-->
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
</configuration>
23 changes: 20 additions & 3 deletions geode-dotnet.sln
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.37216.2 d17.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Geode.Client", "src\Geode.Client\Geode.Client.csproj", "{11111111-1111-1111-1111-111111111111}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Geode.Client.Tests", "tests\Geode.Client.Tests\Geode.Client.Tests.csproj", "{22222222-2222-2222-2222-222222222222}"
Expand All @@ -10,15 +12,21 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Geode.Client.Sample", "samp
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution items", "solution items", "{55555555-5555-5555-5555-555555555555}"
ProjectSection(SolutionItems) = preProject
Directory.Build.props = Directory.Build.props
Directory.Packages.props = Directory.Packages.props
.editorconfig = .editorconfig
.gitignore = .gitignore
CLAUDE.md = CLAUDE.md
README.md = README.md
Directory.Build.props = Directory.Build.props
Directory.Packages.props = Directory.Packages.props
docker-compose.yml = docker-compose.yml
README.md = README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{6FB7AB5D-1656-469E-B35C-566371134193}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{5CF6504F-03FC-4A37-A2A9-32A9547B6D5A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -42,4 +50,13 @@ Global
{44444444-4444-4444-4444-444444444444}.Release|Any CPU.ActiveCfg = Release|Any CPU
{44444444-4444-4444-4444-444444444444}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{11111111-1111-1111-1111-111111111111} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{22222222-2222-2222-2222-222222222222} = {6FB7AB5D-1656-469E-B35C-566371134193}
{33333333-3333-3333-3333-333333333333} = {6FB7AB5D-1656-469E-B35C-566371134193}
{44444444-4444-4444-4444-444444444444} = {5CF6504F-03FC-4A37-A2A9-32A9547B6D5A}
EndGlobalSection
EndGlobal
1 change: 1 addition & 0 deletions samples/Geode.Client.Sample/Program.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

Expand Down
169 changes: 169 additions & 0 deletions src/Geode.Client/Protocol/BigEndianBinaryReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
using System.Buffers.Binary;

namespace Geode.Client.Protocol;

/// <summary>
/// Sequential big-endian reader over an in-memory buffer.
/// C# counterpart of cppcache <c>DataInput</c> / <c>java.io.DataInput</c>:
/// every multi-byte primitive is decoded from network byte order, matching
/// what a Geode server sends.
/// </summary>
/// <remarks>
/// Not thread-safe. Single consumer, read-only. Throws
/// <see cref="EndOfStreamException"/> when a read would go past the end of
/// the buffer.
///
/// BCL's <c>System.IO.BinaryReader</c> is little-endian, hence the explicit
/// "BigEndian" prefix on this type — do not confuse the two.
///
/// Methods marked "prototype" throw <see cref="NotImplementedException"/>
/// and will be filled in as later phases need them.
/// </remarks>
internal sealed class BigEndianBinaryReader
{
private readonly ReadOnlyMemory<byte> _buffer;
private int _position;

public BigEndianBinaryReader(ReadOnlyMemory<byte> buffer)
{
_buffer = buffer;
}

/// <summary>Current byte offset within the buffer.</summary>
public int Position => _position;

/// <summary>Total length of the underlying buffer.</summary>
public int Length => _buffer.Length;

/// <summary>Bytes left to read from the current <see cref="Position"/>.</summary>
public int Remaining => _buffer.Length - _position;

// ======================================================================
// Implemented (Phase 1 — frame codec)
// ======================================================================

/// <summary>Read a single unsigned byte (u8).</summary>
public byte ReadByte()
{
EnsureAvailable(sizeof(byte));
var value = _buffer.Span[_position];
_position += sizeof(byte);
return value;
}

/// <summary>Read a single byte and interpret it as a boolean (0 = false, anything else = true).</summary>
public bool ReadBool() => ReadByte() != 0;

/// <summary>Read a 32-bit signed integer in big-endian byte order.</summary>
public int ReadInt32()
{
EnsureAvailable(sizeof(int));
var value = BinaryPrimitives.ReadInt32BigEndian(_buffer.Span.Slice(_position, sizeof(int)));
_position += sizeof(int);
return value;
}

/// <summary>Read a 64-bit signed integer in big-endian byte order.</summary>
public long ReadInt64()
{
EnsureAvailable(sizeof(long));
var value = BinaryPrimitives.ReadInt64BigEndian(_buffer.Span.Slice(_position, sizeof(long)));
_position += sizeof(long);
return value;
}

/// <summary>
/// Read a raw byte sequence of the given length. Returns a zero-copy slice
/// of the underlying buffer; do not retain it past the buffer's lifetime.
/// Mirrors cppcache <c>DataInput::readBytesOnly</c>.
/// </summary>
public ReadOnlyMemory<byte> ReadBytesOnly(int count)
{
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(count), count, "Length must be non-negative.");
EnsureAvailable(count);
var slice = _buffer.Slice(_position, count);
_position += count;
return slice;
}

// ======================================================================
// Prototype — additional primitives, fill in when first needed
// ======================================================================

/// <summary>Read a signed 8-bit integer (i8).</summary>
public sbyte ReadSByte() =>
throw new NotImplementedException("Phase 2 handshake.");

/// <summary>Read a 16-bit signed integer in big-endian byte order.</summary>
public short ReadInt16() =>
throw new NotImplementedException("Phase 2 handshake / Phase 4 typed values.");

/// <summary>Read a 16-bit unsigned integer in big-endian byte order. Mirrors cppcache <c>readChar</c>.</summary>
public ushort ReadUInt16() =>
throw new NotImplementedException("Phase 4 typed values.");

/// <summary>Read a 32-bit unsigned integer in big-endian byte order.</summary>
public uint ReadUInt32() =>
throw new NotImplementedException("Phase 4 typed values.");

/// <summary>Read a 64-bit unsigned integer in big-endian byte order.</summary>
public ulong ReadUInt64() =>
throw new NotImplementedException("Phase 4 typed values.");

/// <summary>Read an IEEE 754 single-precision float in big-endian byte order.</summary>
public float ReadFloat() =>
throw new NotImplementedException("Phase 4 typed values.");

/// <summary>Read an IEEE 754 double-precision float in big-endian byte order.</summary>
public double ReadDouble() =>
throw new NotImplementedException("Phase 4 typed values.");

/// <summary>
/// Read a length-prefixed byte sequence: i32 length followed by the bytes.
/// Returns <c>null</c> if the length sentinel is <c>-1</c>.
/// Mirrors cppcache <c>DataInput::readBytes</c>.
/// </summary>
public byte[]? ReadBytes() =>
throw new NotImplementedException("Phase 3 Put/Get value parts.");

/// <summary>
/// Read Geode's variable-length array length encoding (1, 2, or 4 bytes).
/// Mirrors cppcache <c>DataInput::readArrayLen</c>.
/// </summary>
public int ReadArrayLen() =>
throw new NotImplementedException("Phase 4 collection-bearing parts.");

/// <summary>
/// Read a Java modified UTF-8 string with a u16 byte-length prefix.
/// Mirrors cppcache <c>DataInput::readUTF</c>.
/// </summary>
/// <remarks>
/// Modified UTF-8 differs from standard UTF-8: <c>0xC0 0x80</c> decodes
/// to <c>\0</c>, and supplementary codepoints arrive as a surrogate pair
/// of two 3-byte sequences (6 bytes total) rather than the 4-byte UTF-8
/// form.
/// </remarks>
public string? ReadJavaModifiedUtf8() =>
throw new NotImplementedException("Phase 4 string values.");

/// <summary>
/// Read a UTF-16 big-endian string with an i32 byte-length prefix.
/// Mirrors cppcache <c>DataInput::readUtf16Huge</c>.
/// </summary>
public string? ReadUtf16Huge() =>
throw new NotImplementedException("Phase 4 large string values.");

// ======================================================================
// Internal helpers
// ======================================================================

private void EnsureAvailable(int needed)
{
if (_position + needed > _buffer.Length)
{
throw new EndOfStreamException(
$"Tried to read {needed} byte(s) at position {_position}, but only {_buffer.Length - _position} remain.");
}
}
}
Loading