Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3517802
Create LICENSE
long45343 Aug 18, 2026
5a7fe74
Add cross-platform release CI
long45343 Aug 18, 2026
3a28a44
Fix server protocol bugs
long45343 Aug 18, 2026
701828c
Bump version to 0.2.1
long45343 Aug 18, 2026
248a635
Release v0.2.5
long45343 Aug 21, 2026
eeefa35
add spec of server
long45343 Aug 22, 2026
eb0440e
Fix sliding window login limiter queue cleanup
long45343 Aug 22, 2026
31b8646
Add constant-time dummy hash verification for non-existent users
long45343 Aug 22, 2026
24b5963
Optimize SeenIdRing with hash map index and FIFO circular queue
long45343 Aug 22, 2026
b782009
Use native .NET APIs for PEM certificate and key loading
long45343 Aug 22, 2026
30362ae
Relocate CLI single-instance lock adjacent to users.json file
long45343 Aug 22, 2026
bedbca7
Split SyncServer into domain-oriented files and folders
long45343 Aug 22, 2026
b831873
Decouple UserHub from SyncServer using IConnectionCoordinator
long45343 Aug 22, 2026
79dadd4
Lock StartIfIdle and guard UserHub consumer loop against re-entrancy
long45343 Aug 22, 2026
d65efed
Add file watcher for hot-reloading users.json
long45343 Aug 22, 2026
d818739
Eliminate frame.ToArray in WebSocket JSON message parsing
long45343 Aug 22, 2026
98fe7e8
Add end-to-end WebSocket integration tests and ServerHost.CreateApp
long45343 Aug 22, 2026
9a1c668
Add Keep a Changelog CHANGELOG and update documentation
long45343 Aug 22, 2026
f9b7875
Release v0.3.0
long45343 Aug 22, 2026
9ed6eba
Refactor RuntimeStateStore to lock-free CAS and periodic background f…
long45343 Aug 22, 2026
fb33861
Release v0.4.0: contract/network test suites, spec alignment, CI split
long45343 Aug 27, 2026
dce49ce
docs(spec): pin client E2E payload convention (nonce fixed 12-byte, t…
long45343 Aug 27, 2026
ceb8642
docs: add bilingual performance test specification (perf.md)
long45343 Aug 28, 2026
93f4b34
docs: perf.md becomes actual measured performance report (v0.4.0)
long45343 Aug 28, 2026
d0f36f3
fix: Windows TLS with persisted certificate keys; perf.md Windows 100…
long45343 Aug 28, 2026
19647e6
docs: clarify S2 memory attribution - marginal cost is ~240 KB/conn
long45343 Aug 28, 2026
740dae5
docs: S5 root cause - zero-swap memory-pressure freeze, not steady-st…
long45343 Aug 28, 2026
b913492
文档更新
long45343 Sep 2, 2026
197cfef
chore: stop tracking specs/ as private local directory
long45343 Sep 4, 2026
f4b7fe4
refactor: replace hand-rolled helpers with BCL equivalents (v0.5.0)
long45343 Sep 4, 2026
c884001
文档修改
long45343 Sep 4, 2026
cbe9071
Update ci.yml
long45343 Sep 5, 2026
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
64 changes: 64 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: CI

on:
push:
branches: [csharp]
pull_request:
branches: [csharp]
workflow_dispatch:

jobs:
build-test:
name: Build and test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.x
dotnet-quality: ga

- name: Restore
run: dotnet restore TextCascade.Server.slnx

- name: Build
run: dotnet build TextCascade.Server.slnx --configuration Release --no-restore

- name: Test
run: >
dotnet test TextCascade.Server.slnx --configuration Release --no-build
--filter "Category!=NetworkIntegration"
--logger trx --results-directory ./TestResults

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: ./TestResults
if-no-files-found: warn

network-tests:
name: Network integration tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.x
dotnet-quality: ga

- name: Restore
run: dotnet restore TextCascade.Server.slnx

- name: Build
run: dotnet build TextCascade.Server.slnx --configuration Release --no-restore

- name: Test
run: dotnet test TextCascade.Server.slnx --configuration Release --no-build --filter "Category=NetworkIntegration"
109 changes: 109 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
name: Release

on:
push:
tags:
- 'v*.*.*'
workflow_dispatch:

permissions:
contents: write

jobs:
release:
name: Build and publish release
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.x
dotnet-quality: ga

- name: Restore
run: dotnet restore TextCascade.Server.slnx

- name: Build
run: dotnet build TextCascade.Server.slnx --configuration Release --no-restore

- name: Test
run: >
dotnet test TextCascade.Server.slnx --configuration Release --no-build
--filter "Category!=NetworkIntegration"

- name: Resolve version
id: version
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"

- name: Publish Windows framework-dependent single file
run: >
dotnet publish TextCascade.Server/TextCascade.Server.csproj
--configuration Release --runtime win-x64 --self-contained false
-p:PublishSingleFile=true -p:DebugType=embedded
-p:IncludeNativeLibrariesForSelfExtract=true
-p:PublishDir=$GITHUB_WORKSPACE/artifacts/win-x64/

- name: Publish Linux framework-dependent single file
run: >
dotnet publish TextCascade.Server/TextCascade.Server.csproj
--configuration Release --runtime linux-x64 --self-contained false
-p:PublishSingleFile=true -p:DebugType=embedded
-p:IncludeNativeLibrariesForSelfExtract=true
-p:PublishDir=$GITHUB_WORKSPACE/artifacts/linux-x64/

- name: Stage release files
run: |
mkdir -p release/textcascade-server-windows-x64 release/textcascade-server-linux-x64
cp artifacts/win-x64/TextCascade.Server.exe release/textcascade-server-windows-x64/
cp artifacts/linux-x64/TextCascade.Server release/textcascade-server-linux-x64/
cp deploy/textcascade.toml release/textcascade-server-windows-x64/
cp deploy/textcascade.toml release/textcascade-server-linux-x64/
cp deploy/textcascade-server.service release/textcascade-server-linux-x64/
chmod +x release/textcascade-server-linux-x64/TextCascade.Server
file release/textcascade-server-linux-x64/TextCascade.Server
test -f release/textcascade-server-windows-x64/TextCascade.Server.exe
test -f release/textcascade-server-linux-x64/TextCascade.Server

- name: Package Windows archive
run: |
cd release
zip -r ../TextCascade.Server-${{ steps.version.outputs.version }}-windows-x64.zip textcascade-server-windows-x64

- name: Package Linux archive
run: |
cd release
tar -czf ../TextCascade.Server-${{ steps.version.outputs.version }}-linux-x64.tar.gz textcascade-server-linux-x64

- name: Generate checksums
run: |
cd "$GITHUB_WORKSPACE"
sha256sum \
TextCascade.Server-${{ steps.version.outputs.version }}-windows-x64.zip \
TextCascade.Server-${{ steps.version.outputs.version }}-linux-x64.tar.gz \
> checksums-sha256.txt

- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: textcascade-server-${{ steps.version.outputs.version }}
path: |
TextCascade.Server-${{ steps.version.outputs.version }}-windows-x64.zip
TextCascade.Server-${{ steps.version.outputs.version }}-linux-x64.tar.gz
checksums-sha256.txt
if-no-files-found: error

- name: Create GitHub Release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: TextCascade Server ${{ github.ref_name }}
draft: false
prerelease: ${{ contains(github.ref_name, '-') }}
files: |
TextCascade.Server-${{ steps.version.outputs.version }}-windows-x64.zip
TextCascade.Server-${{ steps.version.outputs.version }}-linux-x64.tar.gz
checksums-sha256.txt
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## 项目私有目录,不纳入版本库
specs/
/specs/
.zcode/
.trae/

## .NET 构建产物
Expand Down Expand Up @@ -58,3 +59,4 @@ Desktop.ini
*.log
logs/
tmp/
artifacts/
103 changes: 103 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.5.0] - 2026-09-04

### Changed
- Replaced hand-rolled helpers with standard library equivalents, with no behavior change: `TokenService` base64url codec now uses `System.Buffers.Text.Base64Url` (.NET 9 BCL) instead of a custom encode/decode pair; the `IClock`/`SystemClock` seam was replaced by `System.TimeProvider` (registered in DI, tests pass `TimeProvider.System`); `HeartbeatScannerService` was rewritten from a `System.Threading.Timer` callback to `PeriodicTimer` inside `BackgroundService` (scan exceptions are now logged instead of being unobserved).
- Login request parsing (`AuthService.ParseLoginRequest`) now performs a single strict `JsonSerializer.DeserializeAsync` pass with .NET 10 `AllowDuplicateProperties = false` and `JsonUnmappedMemberHandling.Disallow`, replacing the manual 4 KB chunked read loop plus a double `JsonDocument`/`JsonSerializer` parse; the 16 KB cap is enforced via `IHttpMaxRequestBodySizeFeature` (chunked bodies included). Structural violations previously reported as "Invalid login request." now return "Invalid JSON." — status 400 and the `invalid_request` code are unchanged.
- `SingleInstanceLock` no longer probes or recovers PIDs: the lock file (`users.json.lock`) is opened with `FileMode.OpenOrCreate` + `FileShare.None`, so the OS releases it when the holder process dies and a leftover file from a crash is simply reopened instead of blocking. The PID written into the file is diagnostic only. Contention behavior (3 retries, then graceful failure) is unchanged.

### Fixed
- TLS server authentication failed on Windows with "platform does not support ephemeral keys" (0x8009030E): `CertificateLoader` now loads PFX certificates with a persisted key set (`DefaultKeySet`) on Windows (Linux keeps `EphemeralKeySet`), and PEM-loaded certificates are re-exported to a persisted key on Windows. Spec §2.1's Windows Service hosting shape required this to be usable. Found while benchmarking 1000 concurrent connections on a local Windows deployment.

### Documentation
- perf.md: added the Windows local 1000-connection result — 10-minute hold with 0 errors and all 1000 connections alive, RSS delta ≈211 MB (≈211 KB/connection, one third of the Linux figure); P8 is now verified.
- Rewrote `perf.md` as a bilingual (中文/English) actual performance measurement report for v0.4.0 on the production VPS: 1KB broadcast p95 3.5 ms (8.6× headroom), 512KB p95 103 ms, idle CPU 0.08%, cold start 2 s; memory targets (P1/P2) recorded as unmet and flagged for revision; the 1000-connection scenario is documented as untestable on a 1.6 GB same-host environment after it exhausted memory and forced a VM reset.
- Added `tools/perf_probe.py`, a stdlib-only asyncio WSS probe used for the measurements (hold/latency/slow-consumer scenarios).
- Recorded two implementation findings in `docs/server-spec.md` §15: unbounded shutdown close-handshake wait (34 s stop phase observed) and silent queue-full abort without a disconnect security event.

## [0.4.0] - 2026-08-27

### Added
- Contract test suite with JSON sample corpus (`ContractSamples/`) covering duplicate fields, unknown fields, illegal number forms, depth-4 nesting, and invalid UTF-8, plus byte-level serialization invariants for welcome/clip/ack/ping/error/token payloads.
- Network integration test suite (`Category=NetworkIntegration`, 12 cases) over real Kestrel TLS with runtime-generated self-signed certificates: WSS handshake, TLS 1.2/1.3 protocol probes, random port binding, real frame fragmentation, oversize/zero-length frame closes (1009), server restart with token direct reconnect and persisted-version baseline, snapshot election restore, and graceful-shutdown bye/1001 chain.
- Slow-hash smoke tests (`Category=SlowHash`) exercising the real Argon2 Hash/Verify/NeedsRehash chain with production parameters.
- Unit tests closing spec §10.1 gaps: token duplicate-field/illegal-number/range rejection, CLI watermark allocation, delete-and-recreate watermark behavior, revoke and overflow fail-fast with byte-identical file preservation, `WithVersion` immutability, and behavior-level duplicate-id idempotency (drained token bucket still acks duplicates; reused id with new content treated as fresh message).

### Changed
- CI main test job now includes the SlowHash category and excludes only `Category=NetworkIntegration`, which runs in a dedicated CI job.
- Release workflow test step aligned with the same category filter.
- Server spec (`docs/server-spec.md`) rewritten to match v0.3.5 implementation: hot user-file reload, RuntimeStateStore version persistence, content-comparing clip idempotency semantics, 10-minute idle hub recycling, actual log event fields, and a new implementation-gap ledger (§15). Never-implemented items (benchmark project, `server_stop` event, performance target table) removed.
- Added `specs/test-and-contract-spec.md` (function-level test and contract specification) and `specs/spec-decisions.md` (decision record for the spec alignment).

## [0.3.5] - 2026-08-22

### Changed
- Refactored `RuntimeStateStore` to lock-free memory CAS updates using `ConcurrentDictionary` and background periodic flush (`PeriodicTimer`) with atomic snapshot persistence, eliminating synchronous disk I/O bottlenecks in the clip synchronization pipeline.
- Added graceful shutdown flush hook to `SyncServer.ShutdownAsync` ensuring all pending version increments are flushed upon service stop.
- Added concurrency, background periodic flush, and fault-tolerance unit tests for `RuntimeStateStore`.

## [0.3.0] - 2026-08-22

### Added
- Native ASP.NET Core `CreateApp` factory and comprehensive end-to-end WebSocket integration tests covering handshake, broadcast, snapshots, invalid tokens, and abrupt disconnections.
- Hot-reloading of `users.json` via file system watcher with debounced reload and periodic fallback.
- Constant-time password verification on login for non-existent users using a cached dummy hash.

### Changed
- Refactored `SyncServer` by splitting models, hubs, and hosting services into dedicated domain files (`Models/`, `Hub/`, `Hosting/`).
- Decoupled `UserHub` from `SyncServer` using lightweight `IConnectionCoordinator` interface.
- Protected `UserHub.StartIfIdle` with mutex lock and added single-reader re-entrancy protection to `RunUserLoopAsync`.
- Optimized `SeenIdRing` deduplication with hash map lookup (`Dictionary<string, LatestText?>`) and FIFO circular eviction queue.
- Migrated PEM certificate and private key loading to native .NET APIs (`X509Certificate2.CreateFromPemFile` and `X509Certificate2Collection.ImportFromPemFile`).
- Relocated CLI single-instance lock file adjacent to the target `users.json` (`users.json.lock`).
- Switched WebSocket JSON message parsing to `ReadOnlyMemory<byte>` to eliminate redundant byte array copies (`frame.ToArray()`).

### Fixed
- Fixed sliding window login rate limiter to clean up only expired timestamps from queue head rather than evicting the entire key.

### Security
- Eliminated username existence timing side-channel during login authentication.

## [0.2.5] - 2026-08-22

### Added
- Version persistence across server restarts using `RuntimeStateStore` (`textcascade.state.json`).
- Reconnection snapshot recovery window during server startup.

### Changed
- Refactored snapshot selection tie-breaking and broadcast logic.

### Fixed
- Fixed server protocol bugs in version negotiation and error responses.

## [0.2.1] - 2026-08-18

### Fixed
- Fixed server protocol bugs in message deserialization and error framing.

## [0.2.0] - 2026-08-18

### Added
- Cross-platform release workflows and CI matrix for Linux and Windows single-file binaries.
- Bilingual README and documentation.

## [0.1.0] - 2026-08-18

### Added
- Initial import and baseline release of TextCascade Server with Minimal API, Kestrel WebSocket, Argon2 password hashing, and token authentication.

[Unreleased]: https://github.com/long45343/TextCascade-Server/compare/v0.5.0...HEAD
[0.5.0]: https://github.com/long45343/TextCascade-Server/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/long45343/TextCascade-Server/compare/v0.3.5...v0.4.0
[0.3.5]: https://github.com/long45343/TextCascade-Server/compare/v0.3.0...v0.3.5
[0.3.0]: https://github.com/long45343/TextCascade-Server/compare/v0.2.5...v0.3.0
[0.2.5]: https://github.com/long45343/TextCascade-Server/compare/v0.2.1...v0.2.5
[0.2.1]: https://github.com/long45343/TextCascade-Server/compare/v0.2.0...v0.2.1
[0.2.0]: https://github.com/long45343/TextCascade-Server/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/long45343/TextCascade-Server/releases/tag/v0.1.0
Loading
Loading