1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
use crateffi;
use ;
/// `METHOD_READ` is the method string for read (fetch) operations. It is safe
/// to send on QUIC 0-RTT early data because it is idempotent and has no
/// observable side effects.
pub const METHOD_READ: &str = "read";
/// `METHOD_WRITE` is the method string for creating or replacing a resource.
/// It is not idempotent and must not be sent on 0-RTT early data.
pub const METHOD_WRITE: &str = "write";
/// `METHOD_UPDATE` is the method string for partially modifying an existing
/// resource. It is not idempotent.
pub const METHOD_UPDATE: &str = "update";
/// `METHOD_DELETE` is the method string for removing a resource. It is
/// idempotent (deleting a non-existent resource is a no-op) but is not allowed
/// on 0-RTT because the server must authenticate the caller first.
pub const METHOD_DELETE: &str = "delete";
/// `METHOD_CONNECT` is the method string for the NWEP application-layer
/// handshake message. Every new QUIC connection begins with a `connect`
/// exchange that negotiates capabilities (`max-streams`,
/// `max-message-size`, `compression`) and establishes mutual authentication.
pub const METHOD_CONNECT: &str = "connect";
/// `METHOD_AUTHENTICATE` is the method string for the challenge-response mutual
/// authentication sub-protocol that runs inside the `connect` handshake.
pub const METHOD_AUTHENTICATE: &str = "authenticate";
/// `METHOD_HEARTBEAT` is the method string for keep-alive pings. The server
/// responds with `STATUS_OK` and no body.
pub const METHOD_HEARTBEAT: &str = "heartbeat";
/// `STATUS_OK` is the success status for a request that completed normally and
/// produced a response body (analogous to HTTP 200).
pub const STATUS_OK: &str = "ok";
/// `STATUS_CREATED` is the success status for a request that created a new
/// resource (analogous to HTTP 201). The response may include a `Location`-
/// style header identifying the new resource.
pub const STATUS_CREATED: &str = "created";
/// `STATUS_ACCEPTED` is the success status for a request that was accepted for
/// asynchronous processing but has not yet completed (analogous to HTTP 202).
pub const STATUS_ACCEPTED: &str = "accepted";
/// `STATUS_NO_CONTENT` is the success status for a request that completed
/// successfully but produced no response body (analogous to HTTP 204). Typical
/// for `delete` and `update` operations.
pub const STATUS_NO_CONTENT: &str = "no_content";
/// `STATUS_BAD_REQUEST` is the error status for a request that was malformed or
/// missing required headers (analogous to HTTP 400).
pub const STATUS_BAD_REQUEST: &str = "bad_request";
/// `STATUS_UNAUTHORIZED` is the error status for a request from a caller that
/// has not authenticated or whose credentials are invalid (analogous to HTTP
/// 401).
pub const STATUS_UNAUTHORIZED: &str = "unauthorized";
/// `STATUS_FORBIDDEN` is the error status for a request from an authenticated
/// caller who lacks the permission to perform the operation (analogous to HTTP
/// 403).
pub const STATUS_FORBIDDEN: &str = "forbidden";
/// `STATUS_NOT_FOUND` is the error status for a request targeting a resource or
/// path that does not exist (analogous to HTTP 404).
pub const STATUS_NOT_FOUND: &str = "not_found";
/// `STATUS_CONFLICT` is the error status for a request that conflicts with the
/// current state (e.g., duplicate key registration, concurrent modification;
/// analogous to HTTP 409).
pub const STATUS_CONFLICT: &str = "conflict";
/// `STATUS_RATE_LIMITED` is the error status returned when the caller has
/// exceeded the server's request rate limit. Callers should back off and retry
/// after the delay indicated in the `retry-after` header.
pub const STATUS_RATE_LIMITED: &str = "rate_limited";
/// `STATUS_INTERNAL_ERROR` is the error status for an unexpected server-side
/// failure (analogous to HTTP 500).
pub const STATUS_INTERNAL_ERROR: &str = "internal_error";
/// `STATUS_UNAVAILABLE` is the error status for a transient server-side failure
/// where the caller may retry (analogous to HTTP 503).
pub const STATUS_UNAVAILABLE: &str = "unavailable";
/// `HDR_METHOD` is the `:method` pseudo-header name. It carries the method
/// string (e.g., `"read"`) and must be the first header in every request
/// message.
pub const HDR_METHOD: &str = ":method";
/// `HDR_PATH` is the `:path` pseudo-header name. It carries the request path
/// (e.g., `"/objects/abc123"`) and must appear in every request after
/// `:method`.
pub const HDR_PATH: &str = ":path";
/// `HDR_VERSION` is the `:version` pseudo-header name. It carries the protocol
/// version string (see [`crate::types::PROTO_VER`]) and is sent in `connect`
/// requests to allow version negotiation.
pub const HDR_VERSION: &str = ":version";
/// `HDR_STATUS` is the `:status` pseudo-header name. It carries the response
/// status string (e.g., `"ok"`, `"not_found"`) and must be the first header in
/// every response message.
pub const HDR_STATUS: &str = ":status";
/// `HDR_REQUEST_ID` is the `request-id` header name. It carries a
/// 16-byte random ID (hex-encoded) that uniquely identifies a single
/// request/response exchange, enabling log correlation.
pub const HDR_REQUEST_ID: &str = "request-id";
/// `HDR_CLIENT_ID` is the `client-id` header name. It carries the hex-encoded
/// [`crate::types::NodeId`] of the connecting client, sent in the `connect`
/// request.
pub const HDR_CLIENT_ID: &str = "client-id";
/// `HDR_SERVER_ID` is the `server-id` header name. It carries the hex-encoded
/// [`crate::types::NodeId`] of the server, returned in the `connect` response.
pub const HDR_SERVER_ID: &str = "server-id";
/// `HDR_CHALLENGE` is the `challenge` header name. It carries a random nonce
/// (see [`crate::types::CHALLENGE_LEN`]) sent by the server during the
/// `authenticate` sub-protocol, which the client must sign to prove key
/// possession.
pub const HDR_CHALLENGE: &str = "challenge";
/// `HDR_CHALLENGE_RESPONSE` is the `challenge-response` header name. It carries
/// the client's Ed25519 signature over the server's challenge nonce.
pub const HDR_CHALLENGE_RESPONSE: &str = "challenge-response";
/// `HDR_SERVER_CHALLENGE` is the `server-challenge` header name. It carries the
/// server-side challenge nonce sent to the client during mutual authentication.
pub const HDR_SERVER_CHALLENGE: &str = "server-challenge";
/// `HDR_AUTH_RESPONSE` is the `auth-response` header name. It carries the
/// client's response to the server challenge during mutual authentication.
pub const HDR_AUTH_RESPONSE: &str = "auth-response";
/// `HDR_MAX_STREAMS` is the `max-streams` header name. Both peers advertise
/// their maximum concurrent stream count during `connect` negotiation; the
/// minimum of the two values is used.
pub const HDR_MAX_STREAMS: &str = "max-streams";
/// `HDR_MAX_MESSAGE_SIZE` is the `max-message-size` header name. Both peers
/// advertise their maximum message body size during `connect`; the minimum is
/// used for the session.
pub const HDR_MAX_MESSAGE_SIZE: &str = "max-message-size";
/// `HDR_COMPRESSION` is the `compression` header name. It advertises the
/// compression algorithms the peer supports (e.g., `"zstd"`). An empty or
/// absent value means no compression.
pub const HDR_COMPRESSION: &str = "compression";
/// `HDR_ROLES` is the `roles` header name. The server advertises its
/// [`crate::role::ServerRole`] string (e.g., `"log_server"`) in the `connect`
/// response so clients can discover server capabilities.
pub const HDR_ROLES: &str = "roles";
/// `HDR_TRANSCRIPT_SIG` is the `transcript-signature` header name. It carries
/// each peer's Ed25519 signature over the complete handshake transcript,
/// providing mutual authentication and binding the TLS session to the NWEP
/// identity layer.
pub const HDR_TRANSCRIPT_SIG: &str = "transcript-signature";
/// `HDR_STATUS_DETAILS` is the `status-details` header name. It carries a
/// human-readable or machine-parseable string that provides additional context
/// for a non-`ok` status code.
pub const HDR_STATUS_DETAILS: &str = "status-details";
/// `HDR_RETRY_AFTER` is the `retry-after` header name. When the server returns
/// [`STATUS_RATE_LIMITED`] or [`STATUS_UNAVAILABLE`], this header contains a
/// duration (in seconds) after which the client should retry.
pub const HDR_RETRY_AFTER: &str = "retry-after";
/// `HDR_TRACE_ID` is the `trace-id` header name. It carries a 16-byte random
/// distributed trace ID (hex-encoded) that propagates across all hops of a
/// multi-server request chain, enabling end-to-end tracing.
pub const HDR_TRACE_ID: &str = "trace-id";
/// `HDR_EVENT` is the `:event` pseudo-header name. It identifies the event type
/// in server-initiated notification messages (`MSG_NOTIFY`).
pub const HDR_EVENT: &str = ":event";
/// `HDR_NOTIFY_ID` is the `notify-id` header name. It carries a 16-byte random
/// ID that uniquely identifies a server-push notification, enabling
/// deduplication on the client side.
pub const HDR_NOTIFY_ID: &str = "notify-id";
/// `method_is_valid` returns `true` if `method` is a recognized NWEP method
/// name.
///
/// The check is delegated to the C library, which accepts exactly the seven
/// method strings defined as `METHOD_*` constants in this module.
///
/// # Example
///
/// ```rust
/// use nwep::protocol::{method_is_valid, METHOD_READ};
///
/// assert!(method_is_valid(METHOD_READ));
/// assert!(!method_is_valid("GET"));
/// ```
/// `method_is_idempotent` returns `true` if repeating `method` produces the
/// same result as calling it once.
///
/// Currently `"read"` and `"delete"` are considered idempotent. Idempotent
/// methods are safe to retry automatically after a transient network failure.
///
/// # Example
///
/// ```rust
/// use nwep::protocol::method_is_idempotent;
///
/// assert!(method_is_idempotent("read"));
/// assert!(method_is_idempotent("delete"));
/// assert!(!method_is_idempotent("write"));
/// ```
/// `method_allowed_0rtt` returns `true` if `method` is safe to send in QUIC
/// 0-RTT (early data) before the handshake completes.
///
/// Only `"read"` qualifies: it is both idempotent and carries no side effects,
/// so replaying it in the event of a 0-RTT rejection is harmless. All other
/// methods must wait for the full QUIC handshake.
///
/// # Example
///
/// ```rust
/// use nwep::protocol::method_allowed_0rtt;
///
/// assert!(method_allowed_0rtt("read"));
/// assert!(!method_allowed_0rtt("write"));
/// assert!(!method_allowed_0rtt("delete"));
/// ```
/// `status_is_valid` returns `true` if `status` is a recognized NWEP status
/// string.
///
/// # Example
///
/// ```rust
/// use nwep::protocol::status_is_valid;
///
/// assert!(status_is_valid("ok"));
/// assert!(!status_is_valid("200"));
/// ```
/// `status_is_success` returns `true` if `status` indicates a successful
/// outcome.
///
/// The following statuses are considered successful: `"ok"`, `"created"`,
/// `"accepted"`, and `"no_content"`. All other recognized statuses are
/// considered errors.
///
/// # Example
///
/// ```rust
/// use nwep::protocol::status_is_success;
///
/// assert!(status_is_success("ok"));
/// assert!(status_is_success("created"));
/// assert!(status_is_success("accepted"));
/// assert!(status_is_success("no_content"));
/// assert!(!status_is_success("not_found"));
/// ```
/// `status_is_error` returns `true` if `status` indicates a failure outcome.
///
/// This is the logical complement of [`status_is_success`] for recognized
/// status strings. Unrecognized strings return `false` from both functions.
///
/// # Example
///
/// ```rust
/// use nwep::protocol::status_is_error;
///
/// assert!(status_is_error("not_found"));
/// assert!(status_is_error("internal_error"));
/// assert!(!status_is_error("ok"));
/// ```
/// `trace_id_generate` generates a cryptographically random 16-byte trace ID.
///
/// Trace IDs propagate through multi-hop request chains via the [`HDR_TRACE_ID`]
/// header, enabling distributed tracing. Each new outbound request chain should
/// generate a fresh trace ID. The bytes are sourced from OpenSSL's RAND, so the
/// function may fail if the entropy pool is not yet seeded.
///
/// # Errors
///
/// Returns [`crate::Error`] if the C library's random number generation fails.
///
/// # Example
///
/// ```rust
/// let tid = nwep::protocol::trace_id_generate().unwrap();
/// assert_eq!(tid.len(), 16);
/// ```
/// `request_id_generate` generates a cryptographically random 16-byte request
/// ID.
///
/// Request IDs are attached to individual request/response pairs via the
/// [`HDR_REQUEST_ID`] header and allow log entries from the client and server
/// to be correlated for a single RPC. Unlike trace IDs, request IDs are
/// generated per-request rather than per-chain.
///
/// # Errors
///
/// Returns [`crate::Error`] if the C library's random number generation fails.
///
/// # Example
///
/// ```rust
/// let rid = nwep::protocol::request_id_generate().unwrap();
/// assert_eq!(rid.len(), 16);
/// ```
/// `err_to_status` maps a numeric NWEP error code to its corresponding protocol
/// status string.
///
/// This is a standalone function equivalent to [`crate::Error::to_status`] that
/// works without constructing a full [`crate::Error`] value. It is useful in
/// server handlers that already hold an error code and need to produce a
/// response status header.
///
/// Returns `"internal_error"` if `code` has no mapping or the C library returns
/// a null pointer.
///
/// # Example
///
/// ```rust
/// use nwep::protocol::err_to_status;
/// use nwep::error::ERR_NETWORK_CONN_FAILED;
///
/// assert_eq!(err_to_status(ERR_NETWORK_CONN_FAILED), "internal_error");
/// ```
/// `put_uint32be` writes `n` as a 4-byte big-endian integer into the first 4
/// bytes of `buf`.
///
/// This mirrors the C library's `nwep_put_uint32be` and is used when manually
/// constructing NWEP frame headers.
///
/// # Panics
///
/// Panics if `buf.len() < 4`.
/// `get_uint32be` reads a 4-byte big-endian integer from the first 4 bytes of
/// `buf` and returns it as a `u32`.
///
/// This mirrors the C library's `nwep_get_uint32be` and is used when parsing
/// NWEP frame headers.
///
/// # Panics
///
/// Panics if `buf.len() < 4`.
/// `put_uint16be` writes `n` as a 2-byte big-endian integer into the first 2
/// bytes of `buf`.
///
/// # Panics
///
/// Panics if `buf.len() < 2`.
/// `get_uint16be` reads a 2-byte big-endian integer from the first 2 bytes of
/// `buf` and returns it as a `u16`.
///
/// # Panics
///
/// Panics if `buf.len() < 2`.