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
//! yEnc encoding and decoding.
//!
//! # Background
//!
//! yEnc (short for "Why Encode?") was developed by Jürgen Helbing and released
//! in 2001 as a replacement for UUencode and Base64 on Usenet. The key insight
//! is that most byte values (252 out of 256) pass through the encoding with
//! only a single-byte overhead (add 42, mod 256), instead of the 33% overhead
//! of Base64 or the similar overhead of UUencode. In practice yEnc articles are
//! only 1–2% larger than the raw binary they carry.
//!
//! yEnc became the dominant Usenet binary encoding in the early 2000s and
//! remains in use today, especially in NZB-based binary download ecosystems.
//!
//! # Two use cases
//!
//! 1. **Single-part articles** — the entire file is encoded in one article.
//! The body contains `=ybegin`, the encoded data, and `=yend` with a CRC32.
//! Use [`decode`] and [`encode`].
//!
//! 2. **Multi-part articles** — large files are split across numbered articles.
//! Each article carries `=ybegin part= total=`, a `=ypart begin= end=` line
//! giving its byte range, the encoded data, and `=yend` with a per-part CRC32
//! (`pcrc32=`) and optionally the whole-file CRC32 (`crc32=`).
//! Use [`decode`] on each article individually, then reassemble using the
//! [`yencoding_multi`](https://crates.io/crates/yencoding-multi) crate.
//!
//! # Relationship to other crates in this workspace
//!
//! - [`uuencoding`](https://crates.io/crates/uuencoding) — handles the older
//! UUencode format. yEnc replaced UUencode on Usenet but both still appear
//! in email archives.
//! - `yencoding-multi` — multi-part reassembly for yEnc, analogous to
//! `uuencoding-multi` for UUencode. This crate is a dependency of that one.
//!
//! This crate has **no dependency** on `mime-tree`, `uuencoding`, or any other
//! workspace crate.
//!
//! # Not in scope
//!
//! - **NZB files** — NZB is an XML format that describes which Usenet articles
//! carry the parts of a file. Parsing NZB is a separate concern; this crate
//! operates on raw article bodies, not NZB metadata.
//! - **NNTP protocol** — fetching articles from an NNTP server is the caller's
//! responsibility. This crate operates on byte slices.
//! - **SIMD optimisation** — the byte transform is a single subtraction per
//! byte and already vectorises automatically at `-O2`; explicit SIMD adds
//! complexity without measurable benefit.
//!
//! # Security note
//!
//! Unlike Base64, yEnc-encoded data is approximately the same size as the
//! original binary (1–2% overhead). There is no significant size amplification
//! from decoding. However, decoded bytes may represent a compressed archive
//! (`.tar.gz`, `.zip`, `.rar`, etc.). **This crate never decompresses the
//! output.** Any subsequent decompression is the caller's responsibility and
//! must be independently guarded against decompression-bomb attacks before
//! beginning decompression.
//!
//! # Quick start
//!
//! ```rust
//! // Decode a single-part yEnc article.
//! // Oracle: bytes [0,1,2] encode as ['*','+',','] (add 42, no escapes needed).
//! // CRC32 of [0,1,2]: python3 -c "import binascii; print(hex(binascii.crc32(bytes([0,1,2]))&0xffffffff))"
//! // → 0x0854897f
//! let raw_article: &[u8] = b"\
//! =ybegin line=128 size=3 name=hi.bin\r\n\
//! *+,\r\n\
//! =yend size=3 crc32=0854897f\r\n";
//! let part = yencoding::decode(raw_article).unwrap();
//! assert_eq!(part.data, &[0u8, 1, 2]);
//! assert_eq!(part.metadata.filename, "hi.bin");
//! assert!(part.crc32_verified);
//! ```
//!
//! ```rust
//! // Encode bytes as a single-part yEnc article.
//! let encoded = yencoding::encode(b"\x00\x01\x02", "hi.bin", yencoding::DEFAULT_LINE_LENGTH);
//! assert!(encoded.starts_with(b"=ybegin"));
//! assert!(encoded.ends_with(b"\r\n"));
//! ```
pub use DEFAULT_LINE_LENGTH;
pub use YencError;
/// Metadata extracted from a yEnc `=ybegin` header line.
///
/// Common to both single-part and multi-part articles.
/// A successfully decoded yEnc part.
///
/// Returned by [`decode`]. Contains the decoded binary payload, metadata from
/// the article headers, and verification status.
///
/// For single-part articles, `part`, `part_begin`, and `part_end` are all
/// `None`. For multi-part articles they carry the values from `=ybegin part=`
/// and `=ypart begin=/end=`.
/// Decode a yEnc article from raw bytes.
///
/// `input` may begin with arbitrary prose (NNTP article headers, preamble
/// text, etc.) — the decoder scans forward for the first `=ybegin` line.
///
/// # Errors
///
/// - [`YencError::NoHeader`] — no `=ybegin` line found.
/// - [`YencError::InvalidHeader`] — a required field is missing or unparsable.
/// - [`YencError::UnexpectedEof`] — no `=yend` line found.
/// - [`YencError::CrcMismatch`] — CRC mismatch between decoded bytes and
/// the `crc32=` / `pcrc32=` field in `=yend`.
///
/// # Examples
///
/// ```rust
/// // Oracle: [0,1,2] encodes as ['*','+',','] (add 42); CRC32 = 0x0854897f
/// let article: &[u8] = b"\
/// =ybegin line=128 size=3 name=hi.bin\r\n\
/// *+,\r\n\
/// =yend size=3 crc32=0854897f\r\n";
/// let part = yencoding::decode(article).unwrap();
/// assert_eq!(part.data, &[0u8, 1, 2]);
/// assert!(part.crc32_verified);
/// ```
/// Encode `data` as a single-part yEnc article.
///
/// Returns the complete article body including `=ybegin`, encoded data lines,
/// and `=yend` with CRC32. Does **not** include NNTP message headers.
///
/// # Parameters
///
/// - `data` — raw bytes to encode. May be empty.
/// - `filename` — written verbatim to `name=` on `=ybegin`.
/// - `line_length` — encoded bytes per line. Values below 2 are clamped to 2.
/// Use [`DEFAULT_LINE_LENGTH`] (128) unless you have a specific reason to deviate.
///
/// # Examples
///
/// ```rust
/// // Oracle: [0,1,2] encodes as ['*','+',',']; CRC32 = 0x0854897f
/// let encoded = yencoding::encode(b"\x00\x01\x02", "hi.bin", yencoding::DEFAULT_LINE_LENGTH);
/// assert!(encoded.starts_with(b"=ybegin"));
/// let part = yencoding::decode(&encoded).unwrap();
/// assert_eq!(part.data, b"\x00\x01\x02");
/// assert!(part.crc32_verified);
/// ```
/// Parameters for encoding one part of a multi-part yEnc series.
///
/// Used with [`encode_part`]. All fields are required; there are no defaults
/// because all values must be derived from the caller's knowledge of the full
/// file split.
///
/// # Example
///
/// ```rust
/// let full_data = b"\x00\x01\x02\x03\x04\x05";
/// let whole_crc = crc32fast::hash(full_data);
///
/// let opts = yencoding::EncodePartOptions {
/// filename: "f.bin",
/// total_size: 6,
/// total_parts: 2,
/// part: 1,
/// begin: 1,
/// end: 3,
/// whole_file_crc32: whole_crc,
/// line_length: yencoding::DEFAULT_LINE_LENGTH,
/// };
/// let encoded = yencoding::encode_part(&full_data[..3], &opts);
/// let p = yencoding::decode(&encoded).unwrap();
/// assert_eq!(p.part, Some(1));
/// assert_eq!(p.metadata.total_parts, Some(2));
/// ```
/// Encode one part of a multi-part yEnc series.
///
/// `data` is the raw bytes of **this part only** (not the whole file).
/// All other parameters are in `opts`; see [`EncodePartOptions`].
///
/// Returns the complete part article body including `=ybegin`, `=ypart`,
/// encoded data, and `=yend` with `pcrc32=` and `crc32=`.