Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
9345e76
chore: 🤖 cleanup imports
streamich Oct 9, 2025
fc05dda
test: 💍 update TCP server demo
streamich Oct 9, 2025
dbca0e7
chore: 🤖 small tweaks
streamich Oct 9, 2025
ca8cd4d
feat: 🎸 use XdrType on some data types
streamich Oct 9, 2025
b089226
refactor: 💡 add `.encode()` methods to XDR data types
streamich Oct 9, 2025
5594aee
feat: 🎸 inline `.encode()` methods remaining XDR data types
streamich Oct 9, 2025
77840d2
feat: 🎸 cleanup struct encoders
streamich Oct 9, 2025
ef295ec
fix: 🐛 fixup XDR types, add roundtrip types
streamich Oct 9, 2025
513d89d
feat: 🎸 add decoder for callback procedures
streamich Oct 9, 2025
e20ff0a
feat: 🎸 improve RM record framing
streamich Oct 10, 2025
23a5cca
feat: 🎸 start server and connection implementation
streamich Oct 10, 2025
832884d
style: 💄 run Prettier
streamich Oct 10, 2025
f611976
refactor: 💡 move server to server/ sub-folder
streamich Oct 10, 2025
46556ee
feat: 🎸 add sample TCP client implementation
streamich Oct 10, 2025
9b994a4
feat: 🎸 implement COMPOUND operation executor
streamich Oct 11, 2025
61040c6
perf: ⚡ ️improve COMPOUND procedure execution flow
streamich Oct 11, 2025
f623efa
style: 💄 run Prettier
streamich Oct 11, 2025
a161b4c
feat: 🎸 chec response type is valid
streamich Oct 11, 2025
d9ab762
feat: 🎸 add operation stub implementation
streamich Oct 11, 2025
fa027f7
feat: 🎸 setup Node.js NFS v4 server implementation stub
streamich Oct 11, 2025
24eb591
feat: 🎸 add initial SETCLIENTID implementation
streamich Oct 11, 2025
f09b1a3
refactor: 💡 move operations under operations/ folder
streamich Oct 11, 2025
8b10c2c
feat: 🎸 update SETCLIENTID implementation
streamich Oct 11, 2025
65a0247
feat: 🎸 add principal and cache handlign in SETCLIENTID
streamich Oct 11, 2025
a226d87
feat: 🎸 improve SETCLIENTID_CONFIRM implementation
streamich Oct 11, 2025
5d1a033
feat: 🎸 add debug logging
streamich Oct 11, 2025
52b1bfa
chore: 🤖 fix imports
streamich Oct 11, 2025
c52adc1
feat: 🎸 implement FileHandleMapper
streamich Oct 11, 2025
f50a794
feat: 🎸 add more file handle and lookup operations
streamich Oct 12, 2025
1b9e2ec
feat: 🎸 update compound procedure logging
streamich Oct 12, 2025
d42855e
feat: 🎸 add initial GETATTR implementation
streamich Oct 12, 2025
a9a6dd6
feat: 🎸 add attribute classification
streamich Oct 12, 2025
9c63340
feat: 🎸 improve attribute encoding
streamich Oct 12, 2025
0da807d
feat: 🎸 add initial formatter implementation
streamich Oct 12, 2025
3037c09
refactor: 💡 rename full encoder
streamich Oct 12, 2025
300e3b7
fix: 🐛 correctly construct absolute path
streamich Oct 13, 2025
5fd580c
feat: 🎸 implement formatters
streamich Oct 13, 2025
6ac80a1
feat: 🎸 add `ACCESS` operation implementation
streamich Oct 13, 2025
8cde8bf
feat: 🎸 update formatting for attributes
streamich Oct 13, 2025
4f98390
feat: 🎸 fix encoder test
streamich Oct 13, 2025
ff4652b
feat: 🎸 implement READDIR method
streamich Oct 13, 2025
cf44e18
test: 💍 setup integration tests
streamich Oct 13, 2025
940915f
test: 💍 fix tests
streamich Oct 13, 2025
72fa8b4
feat: 🎸 add procedure builder
streamich Oct 13, 2025
606e00c
feat: 🎸 add file OPEN and CLOSE operations
streamich Oct 13, 2025
3f4eea4
feat: 🎸 implement locking operations
streamich Oct 13, 2025
d23dc6d
feat: 🎸 implement READ/WRITE methods
streamich Oct 13, 2025
fcabe8f
feat: 🎸 add remaining NFS Node.js operations
streamich Oct 13, 2025
da43e56
feat: 🎸 fix up paths
streamich Oct 13, 2025
ecd297d
feat: 🎸 setup fs client
streamich Oct 13, 2025
d7e524b
fix: 🐛 fix UTF8 encoding test
streamich Oct 13, 2025
a6f6e5e
feat: 🎸 improve JSON codec string encoding
streamich Oct 13, 2025
98a88f9
feat: 🎸 add initial read file and write file operations
streamich Oct 13, 2025
b8bae4c
feat: 🎸 add support for file creation
streamich Oct 13, 2025
e98e696
feat: 🎸 add more promises api methods
streamich Oct 13, 2025
72cecfb
feat: 🎸 implement `.stat()` method
streamich Oct 13, 2025
675e301
feat: 🎸 add directory traversal and creation methods
streamich Oct 13, 2025
20f8883
feat: 🎸 add more methods
streamich Oct 13, 2025
0f9ba55
feat: 🎸 add more methods
streamich Oct 13, 2025
de6712e
feat: 🎸 implement `.rm()` method
streamich Oct 13, 2025
55b2d1a
feat: 🎸 implement `.opendir()` method
streamich Oct 13, 2025
8151dff
feat: 🎸 add `.lstat()` implementation
streamich Oct 13, 2025
de20869
feat: 🎸 imlement chown and chmod operations
streamich Oct 13, 2025
2161af2
refactor: 💡 bind methods
streamich Oct 13, 2025
5370d19
feat: 🎸 add FileHandle stub
streamich Oct 13, 2025
72bac16
feat: 🎸 imlement FileHandle methods
streamich Oct 13, 2025
c79ed6c
feat: 🎸 implement bulk write and read methods
streamich Oct 13, 2025
72578bd
feat: 🎸 implement `FileHandle` streaming methods
streamich Oct 13, 2025
ab5a953
fix: 🐛 improve seqid handling
streamich Oct 14, 2025
bebfd2f
feat: 🎸 improve attribute handling
streamich Oct 14, 2025
2a255ab
feat: 🎸 support illegal operations in COMPOUND
streamich Oct 14, 2025
d7dc7df
fix: 🐛 always advance seqid
streamich Oct 14, 2025
2aad186
feat: 🎸 improve handling of file OPEN flags
streamich Oct 14, 2025
26ce914
feat: 🎸 improve LOCK handling
streamich Oct 14, 2025
1867083
docs ✏️ add more RFCs
streamich Oct 14, 2025
8a313c9
feat: 🎸 improve seqid handling in locking
streamich Oct 14, 2025
9de370f
fix: 🐛 imrpve seqid handling on close
streamich Oct 14, 2025
81e20d0
fix: 🐛 update RENAME behavior
streamich Oct 14, 2025
7031f22
fix: 🐛 keep fh across file renames
streamich Oct 14, 2025
b9fd6df
feat: 🎸 shorten log messages
streamich Oct 14, 2025
eae1a50
feat: 🎸 move and rename AppleDouble file when parent file is moved or…
streamich Oct 14, 2025
a9444f8
feat: 🎸 simplify logging format
streamich Oct 14, 2025
700cc56
fix: 🐛 encode change info in REMOVE and RENAME
streamich Oct 14, 2025
fbaf392
feat: 🎸 improve file handle handling
streamich Oct 14, 2025
f49123f
fix: 🐛 improve seqid handling in OPEN operation
streamich Oct 14, 2025
23d8abd
feat: 🎸 support reporting back file system stats
streamich Oct 14, 2025
30ce6f5
feat: 🎸 update locking architecture
streamich Oct 15, 2025
d0e99ac
test: 💍 add LOCK tests
streamich Oct 15, 2025
6ddf8dc
docs ✏️ add NFS v4.1 and v.4.2 implementation plan
streamich Oct 15, 2025
414e523
feat: 🎸 update decoding for some XDR structures
streamich Oct 15, 2025
95c2607
style: 💄 run Prettier
streamich Oct 15, 2025
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
47 changes: 47 additions & 0 deletions docs/nfs/missing-features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Missing NFS Features Overview

The current code base implements NFSv4.0 semantics. Bringing it to feature parity with NFSv4.1 and NFSv4.2 requires the enhancements summarized below. Use this document as a high-level checklist for product managers, architects, and engineers.

## Legend

- **Status**: ✔️ complete · ⚠️ planned · ❌ missing
- **Impact**: H (high), M (medium), L (low) effort/risk assessment

## NFSv4.1 Gaps

| Feature Area | Status | Impact | Notes |
|--------------|:------:|:------:|-------|
| Session protocol (`EXCHANGE_ID`, `CREATE_SESSION`, `SEQUENCE`, replay cache) | ❌ | H | Must replace legacy `SETCLIENTID` handshake, manage slot tables, introduce session persistence, replay detection, and trunking support per RFC 5661 §18. |
| Backchannel callbacks | ❌ | M | Needed for delegations and pNFS recalls. Requires callback transport negotiation and request dispatch. |
| pNFS layouts and device management | ❌ | H | Implement `GETDEVICELIST`, `GETDEVICEINFO`, `LAYOUTGET/RETURN/COMMIT/ERROR/STATS`, plus registry for device IDs and layout drivers. |
| Layout-aware client tooling | ❌ | M | Client helpers must negotiate layouts, interpret device info, and choose data paths. |
| Recovery improvements (`RECLAIM_COMPLETE`, `FREE_STATEID`, `TEST_STATEID`) | ❌ | M | Enables robust crash recovery and state reclamation. |
| Secret state verification (`SET_SSV`) | ❌ | M | Required for secure session re-establishment. |
| Attribute expansion (layout hints, fs status, etc.) | ❌ | M | Update bitmaps and server responses to include new attributes; affects GETATTR/SETATTR flows. |
| Observability for sessions/pNFS | ❌ | L | Needed to monitor slot exhaustion, recalls, and layout churn. |

## NFSv4.2 Gaps

| Feature Area | Status | Impact | Notes |
|--------------|:------:|:------:|-------|
| Sparse file enhancements (`READ_PLUS`, `ALLOCATE`, `DEALLOCATE`) | ❌ | H | Requires encoder/decoder updates and filesystem support for hole punching and zero-cost reads. |
| Application I/O hints (`IO_ADVISE`, `SEEK`) | ❌ | M | Guides caching and positioning; backend hooks to adjust behavior. |
| Server-side clone/copy (`COPY`, `COPY_NOTIFY`, `OFFLOAD_STATUS`, `CLONE`, `OFFLOAD_CANCEL`) | ❌ | H | Demands asynchronous copy manager, inter-server RPCSEC_GSSv3 support, and backend integration. |
| Extended attributes (`GETXATTR`, `SETXATTR`, `LISTXATTR`, `REMOVEXATTR`) | ❌ | M | Provide POSIX-like xattr functionality with size/error handling. |
| Application Data Block (ADB) support | ❌ | M | Adds data integrity metadata for READ_PLUS transfers. |
| Labeled NFS (`sec_label`, MAC enforcement, operating modes) | ❌ | H | Mandates policy integration, attribute handling, and mode negotiation. |
| Observability for copy/sparse/xattr flows | ❌ | L | Logging/metrics for new operations to aid debugging. |
| Documentation updates | ⚠️ | L | Track new configuration steps, operational guidance, and compatibility matrices. |

## Cross-Cutting Concerns

- **Testing**: Integration suites must be expanded to cover sessions, pNFS, sparse files, server-side copy, xattrs, and labeled access control.
- **Security**: RPCSEC_GSSv3 support is mandatory for secure copy-offload; policy checks must extend to sec_label and SSV workflows.
- **Performance**: Slot table sizing, layout caching, and clone/copy offload should be tuned for high-throughput workloads.

## Next Steps

1. Execute the task breakdowns in `v4.1-implementation-plan.md` and `v4.2-implementation-plan.md`.
2. Prioritize high-impact items (sessions, pNFS, server-side copy) to unlock interoperability with modern NFS clients.
3. Coordinate with operations teams to validate required filesystem/backing service capabilities.
4. Maintain an updated gap tracker as features graduate from planned to completed.
Empty file.
61 changes: 61 additions & 0 deletions docs/nfs/v4.2-implementation-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# NFSv4.2 Implementation Plan

This document extends the v4.1 upgrade effort with the additional requirements defined in RFC 7862. Each task lists a ready-to-use LLM prompt to accelerate implementation.

## Prerequisites

- Completed NFSv4.1 implementation (sessions, pNFS, recovery tooling) and passing integration suite.
- Working knowledge of RFC 7862 sections describing server-side copy, sparse files, application I/O advice, ADB, and labeled NFS.
- Filesystem backend capable of hole punching, cloning, and extended attributes (or mockable interfaces when unavailable).

## Phase 1 · Protocol Additions

| # | Task | Guidance | LLM Prompt |
|---|------|----------|------------|
| 1 | Expand opcode/attribute catalogs | Add v4.2 operations (`ALLOCATE`, `DEALLOCATE`, `READ_PLUS`, `COPY`, etc.) and new attributes (`clone_blksize`, `space_freed`, `change_attr_type`, `sec_label`, etc.) to constants, builders, and attribute maps. | `Update src/nfs/v4/constants.ts, builder.ts, and attributes.ts to include every opcode, error, and attribute introduced in RFC 7862. Ensure bitmap handling scales beyond current word count and extend unit tests covering the new ranges.` |
| 2 | Implement message/struct support | Create classes and serializers for new requests/responses (COPY_NOTIFY, OFFLOAD_STATUS, IO_ADVISE, SEEK, xattr ops, etc.). | `Add TypeScript request/response classes for all NFSv4.2 operations, wiring them into the encoder/decoder stack. Provide fixtures and round-trip tests proving binary compatibility with RFC 7862 examples.` |

## Phase 2 · Sparse File & I/O Enhancements

| # | Task | Guidance | LLM Prompt |
|---|------|----------|------------|
| 3 | READ_PLUS pipeline | Implement READ_PLUS decoding/encoding, supporting data, hole, and sparse-aware elements. | `Implement READ_PLUS handling on both client and server: encode READ_PLUS responses with data and hole segments, decode them client-side, and add tests demonstrating sparse file transfers.` |
| 4 | ALLOCATE/DEALLOCATE semantics | Map allocation and hole-punching requests to filesystem APIs, including quota checks and error handling. | `Wire ALLOCATE and DEALLOCATE operations into the filesystem adapter layer, calling underlying fallocate/punch-hole APIs or mocks. Validate behavior with unit tests and ensure stateids remain consistent.` |
| 5 | IO_ADVISE support | Accept and persist application I/O hints, applying them to caching policies or forwarding to backend drivers. | `Implement IO_ADVISE request processing, store per-file advice, and expose hooks for storage backends to react. Cover key hint types (sequential, random, willneed, dontneed) with tests.` |

## Phase 3 · Server-Side Copy & Clone

| # | Task | Guidance | LLM Prompt |
|---|------|----------|------------|
| 6 | COPY & COPY_NOTIFY workflows | Coordinate inter/intra-server copy lifecycle, including asynchronous state management and error mapping. | `Create a CopyManager coordinating COPY_NOTIFY exchanges, COPY execution, and OFFLOAD_STATUS polling. Handle chunked progress, cancellation, and error propagation per RFC 7862 §4.` |
| 7 | CLONE integration | Support instantaneous clones when backend allows and fall back to server-side copy otherwise. | `Implement the CLONE operation, checking backend capabilities before cloning ranges. If unsupported, fall back to COPY-based replication. Add tests covering both clone-success and copy-fallback paths.` |
| 8 | OFFLOAD controls | Handle `OFFLOAD_CANCEL` and `OFFLOAD_STATUS`, ensuring clients can monitor/cancel long-running copies. | `Add handlers for OFFLOAD_STATUS and OFFLOAD_CANCEL that query the CopyManager and control in-progress copies. Provide tests simulating cancellation mid-transfer and status polling.` |

## Phase 4 · Extended Attributes & Data Integrity

| # | Task | Guidance | LLM Prompt |
|---|------|----------|------------|
| 9 | XATTR operations | Implement `GETXATTR`, `SETXATTR`, `LISTXATTR`, `REMOVEXATTR`, respecting size limits and ACL constraints. | `Implement the xattr operation family, integrating with filesystem adapters for storage/retrieval. Add validation for size limits and permissions, and cover error paths in tests.` |
| 10 | Application Data Block (ADB) | Introduce data block descriptors, integrity metadata, and READ_PLUS examples demonstrating corruption detection. | `Implement the Application Data Block framework: define data block descriptors, support READ_PLUS segments carrying block metadata, and add tests showing detection of mismatched checksums.` |

## Phase 5 · Labeled NFS & Security

| # | Task | Guidance | LLM Prompt |
|---|------|----------|------------|
| 11 | Security label attribute | Add `sec_label` attribute support to GETATTR/SETATTR, including policy translation layers. | `Implement security label handling: parse and set sec_label attributes, integrate with policy engine hooks, and enforce MAC checks on access. Add tests covering label propagation.` |
| 12 | Labeled operation modes | Support Full, Limited Server, and Guest modes, including discovery via attributes and layout considerations. | `Implement Labeled NFS modes: expose capabilities through attributes, gate operations by policy, and ensure layout recall respects label constraints. Cover mode transitions with tests.` |
| 13 | RPCSEC_GSSv3 integration | Enable secure inter-server copy by negotiating RPCSEC_GSSv3 contexts and propagating credentials. | `Integrate RPCSEC_GSSv3 for inter-server copy workflows. Negotiate security contexts during COPY_NOTIFY, wrap copy RPCs, and add tests verifying failure when peers lack support.` |

## Phase 6 · Telemetry & Compliance

| # | Task | Guidance | LLM Prompt |
|---|------|----------|------------|
| 14 | Metrics & logging | Extend observability to cover sparse operations, copy lifecycle, xattr usage, and labeled-mode decisions. | `Extend the logging/metrics framework to emit events for READ_PLUS holes, copy progress, xattr mutations, and label enforcement. Create tests ensuring logs appear with expected fields.` |
| 15 | Integration scenarios | Build end-to-end tests covering sparse IO, server-side copy, xattrs, and labeled NFS enforcement. | `Create integration tests exercising READ_PLUS on sparse files, COPY/CLONE operations, xattr round-trips, and labeled NFS access control. Use fixtures mirroring RFC 7862 examples.` |
| 16 | Documentation & rollout | Update docs with deployment guidance, compatibility notes, and new configuration toggles. | `Document the NFSv4.2 feature set: write setup guides for sparse file support, copy offload, and labeled NFS. Update docs/nfs/ with troubleshooting tips and release notes.` |

## Exit Criteria

- All NFSv4.2 mandatory and recommended features implemented or explicitly flagged as unsupported.
- Integration suite validates sparse file handling, copy offload, xattrs, and security labels.
- Documentation and observability ensure operators can deploy and monitor the new capabilities.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@
"@jsonjoy.com/json-pointer": "^1.0.2",
"@jsonjoy.com/util": "^1.9.0",
"hyperdyperid": "^1.2.0",
"thingies": "^2.5.0"
"thingies": "^2.5.0",
"tree-dump": "^1.1.0"
},
"devDependencies": {
"@msgpack/msgpack": "^3.0.0-beta2",
Expand All @@ -96,6 +97,7 @@
"js-base64": "^3.7.2",
"jsbi": "^4.3.0",
"json-pack-napi": "^0.0.2",
"memfs": "^4.49.0",
"messagepack": "^1.1.12",
"msgpack-lite": "^0.1.26",
"msgpack5": "^6.0.2",
Expand Down
93 changes: 93 additions & 0 deletions src/cbor/__tests__/CborEncoder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,3 +418,96 @@ describe('JsonPackValue', () => {
});
});
});

describe('buffer reallocation stress tests', () => {
test('strings with non-ASCII triggering fallback with small buffer', () => {
const smallWriter = new Writer(64);
const smallEncoder = new CborEncoder(smallWriter);
for (let round = 0; round < 50; round++) {
smallWriter.reset();
for (let i = 0; i < 500; i++) {
const str = 'test_' + i + '_\x00\x01\x02';
const encoded = smallEncoder.encode(str);
const decoded = decode(encoded);
expect(decoded).toBe(str);
}
}
});

test('very long strings that exceed ensureCapacity pre-allocation', () => {
// Use a Writer with initial capacity smaller than what a single string will need
const tinyWriter = new Writer(32);
const tinyEncoder = new CborEncoder(tinyWriter);
for (let round = 0; round < 20; round++) {
tinyWriter.reset();
// Create a string that's long enough to require more than the tiny buffer
const str = 'x'.repeat(100) + '\x00\x01\x02' + 'y'.repeat(100);
const encoded = tinyEncoder.encode(str);
const decoded = decode(encoded);
expect(decoded).toBe(str);
}
});

test('alternating short and long strings with non-ASCII', () => {
const smallWriter = new Writer(64);
const smallEncoder = new CborEncoder(smallWriter);
for (let round = 0; round < 30; round++) {
smallWriter.reset();
for (let i = 0; i < 100; i++) {
// Alternate between short strings with control chars and longer strings
const str = i % 2 === 0 ? 'short_\x00\x01\x02_' + i : 'a'.repeat(50) + '\x03\x04' + 'b'.repeat(50);
const encoded = smallEncoder.encode(str);
const decoded = decode(encoded);
expect(decoded).toBe(str);
}
}
});

test('many iterations with long strings', () => {
const smallWriter = new Writer(64);
const smallEncoder = new CborEncoder(smallWriter);
for (let round = 0; round < 10; round++) {
smallWriter.reset();
for (let i = 0; i < 1000; i++) {
const str = 'a'.repeat(Math.floor(Math.random() * 32768));
const encoded = smallEncoder.encode(str);
const decoded = decode(encoded);
expect(decoded).toBe(str);
}
}
});

test('objects with many short strings', () => {
const smallWriter = new Writer(64);
const smallEncoder = new CborEncoder(smallWriter);
for (let round = 0; round < 100; round++) {
smallWriter.reset();
const obj: Record<string, string> = {};
for (let i = 0; i < 100; i++) {
obj['key_' + i] = 'value_' + i;
}
const encoded = smallEncoder.encode(obj);
const decoded = decode(encoded);
expect(decoded).toEqual(obj);
}
});

test('mixed objects and strings with buffer growth', () => {
const smallWriter = new Writer(64);
const smallEncoder = new CborEncoder(smallWriter);
for (let round = 0; round < 50; round++) {
smallWriter.reset();
const data = {
str1: 'test_\x00\x01',
nested: {
str2: 'nested_\x02\x03',
arr: ['a', 'b', 'c_\x04'],
},
str3: 'final_\x05\x06\x07',
};
const encoded = smallEncoder.encode(data);
const decoded = decode(encoded);
expect(decoded).toEqual(data);
}
});
});
12 changes: 9 additions & 3 deletions src/json/JsonEncoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ export class JsonEncoder implements BinaryJsonEncoder, StreamingBinaryJsonEncode
const length = str.length;
writer.ensureCapacity(length * 4 + 2);
if (length < 256) {
let x = writer.x;
const startX = writer.x;
let x = startX;
const uint8 = writer.uint8;
uint8[x++] = 0x22; // "
for (let i = 0; i < length; i++) {
Expand All @@ -158,15 +159,20 @@ export class JsonEncoder implements BinaryJsonEncoder, StreamingBinaryJsonEncode
break;
}
if (code < 32 || code > 126) {
writer.utf8(JSON.stringify(str));
writer.x = startX;
const jsonStr = JSON.stringify(str);
writer.ensureCapacity(jsonStr.length * 4 + 4);
writer.utf8(jsonStr);
return;
} else uint8[x++] = code;
}
uint8[x++] = 0x22; // "
writer.x = x;
return;
}
writer.utf8(JSON.stringify(str));
const jsonStr = JSON.stringify(str);
writer.ensureCapacity(jsonStr.length * 4 + 4);
writer.utf8(jsonStr);
}

public writeAsciiStr(str: string): void {
Expand Down
73 changes: 73 additions & 0 deletions src/json/__tests__/JsonEncoder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,76 @@ describe('nested object', () => {
});
});
});

describe('buffer reallocation stress tests', () => {
test('strings with non-ASCII triggering fallback (reproduces writer.x bug)', () => {
// This specifically tests the bug where writer.x is not reset before fallback
// When a short string (<256) contains non-ASCII, it triggers writer.utf8()
// but writer.x has already been incremented by writing the opening quote
for (let round = 0; round < 50; round++) {
const smallWriter = new Writer(64);
const smallEncoder = new JsonEncoder(smallWriter);

for (let i = 0; i < 500; i++) {
// Create strings < 256 chars with non-ASCII character to trigger fallback
const asciiPart = 'a'.repeat(Math.floor(Math.random() * 200));
const value = {foo: asciiPart + '\u0001' + asciiPart}; // control char triggers fallback
const encoded = smallEncoder.encode(value);
const json = Buffer.from(encoded).toString('utf-8');
const decoded = JSON.parse(json);
expect(decoded).toEqual(value);
}
}
});

test('many iterations with long strings (reproduces writer.utf8 bug)', () => {
// Run multiple test rounds to increase chance of hitting the bug
for (let round = 0; round < 10; round++) {
const smallWriter = new Writer(64);
const smallEncoder = new JsonEncoder(smallWriter);

for (let i = 0; i < 1000; i++) {
const value = {
foo: 'a'.repeat(Math.round(32000 * Math.random()) + 10),
};
const encoded = smallEncoder.encode(value);
const json = Buffer.from(encoded).toString('utf-8');
const decoded = JSON.parse(json);
expect(decoded).toEqual(value);
}
}
});

test('repeated long strings >= 256 chars (reproduces writer.utf8 bug)', () => {
// Run multiple test rounds to increase chance of hitting the bug
for (let round = 0; round < 20; round++) {
const smallWriter = new Writer(64);
const smallEncoder = new JsonEncoder(smallWriter);

for (let i = 0; i < 100; i++) {
const length = 256 + Math.floor(Math.random() * 10000);
const value = {foo: 'a'.repeat(length)};
const encoded = smallEncoder.encode(value);
const json = Buffer.from(encoded).toString('utf-8');
const decoded = JSON.parse(json);
expect(decoded).toEqual(value);
}
}
});

test('many short strings with buffer growth (reproduces writer.utf8 bug)', () => {
// Run multiple test rounds to increase chance of hitting the bug
for (let round = 0; round < 10; round++) {
const smallWriter = new Writer(64);
const smallEncoder = new JsonEncoder(smallWriter);

for (let i = 0; i < 1000; i++) {
const value = {foo: 'test' + i};
const encoded = smallEncoder.encode(value);
const json = Buffer.from(encoded).toString('utf-8');
const decoded = JSON.parse(json);
expect(decoded).toEqual(value);
}
}
});
});
Loading