14 | --------------------------------------------------------------------------------
15 | -- Length calculations
16 | --------------------------------------------------------------------------------
18 | ||| Given the length of Base64 encoded data, calculate the maximum possible
19 | ||| length of the decoded data.
20 | |||
21 | ||| The result is an upper bound and does not account for padding characters.
22 | |||
23 | ||| The actual decoded length may therefore be smaller.
24 | |||
25 | export
30 | ||| Calculate the maximum number of bytes that can be produced by decoding
31 | ||| a Base64 input of the given length.
32 | |||
33 | ||| This is the natural-number counterpart of `base64DecodeLength` and is used
34 | ||| internally when allocating statically sized mutable output buffers.
35 | |||
40 | --------------------------------------------------------------------------------
41 | -- Internal constants
42 | --------------------------------------------------------------------------------
44 | ||| The ASCII value of the Base64 padding character.
45 | |||
49 | ||| The sentinel value stored in the decoder lookup tables for an invalid
50 | ||| Base64 input byte.
51 | |||
52 | ||| The decoder lookup tables use a 24-bit packed representation.
53 | |||
54 | ||| The high-order bit is set in this value so that invalid table entries can
55 | ||| be detected efficiently after lookup.
56 | |||
60 | --------------------------------------------------------------------------------
61 | -- Internal table access
62 | --------------------------------------------------------------------------------
64 | ||| Look up a decoder contribution for an ASCII input byte.
65 | |||
66 | ||| The decoder tables contain 256 entries, one for every possible byte value.
67 | |||
68 | ||| The input is therefore used directly as an array index.
69 | |||
74 | assert_total $
75 | idris_crash
76 | "Data.String.Base64.Decode.lookupDecodeTable: invalid byte index"
80 | ||| Look up the first packed decoder contribution for a Base64 input byte.
81 | |||
86 | ||| Look up the second packed decoder contribution for a Base64 input byte.
87 | |||
92 | ||| Look up the third packed decoder contribution for a Base64 input byte.
93 | |||
98 | ||| Look up the fourth packed decoder contribution for a Base64 input byte.
99 | |||
104 | --------------------------------------------------------------------------------
105 | -- Decoder table validation
106 | --------------------------------------------------------------------------------
108 | ||| Determine whether a decoder table contribution represents invalid input.
109 | |||
114 | ||| Determine whether a Base64 input byte is the padding character.
115 | |||
120 | ||| Determine whether a byte is a valid Base64 alphabet character.
121 | |||
122 | ||| This checks the optimized lookup tables directly.
123 | |||
124 | ||| Padding is handled separately because it is valid only in the final
125 | ||| quartet.
126 | |||
134 | --------------------------------------------------------------------------------
135 | -- Immutable buffer access
136 | --------------------------------------------------------------------------------
138 | ||| Read a byte from an immutable input buffer at a natural-number offset.
139 | |||
140 | ||| The decoder's traversal guarantees that all requested offsets are within
141 | ||| the bounds of the source buffer. Failure therefore indicates an internal
142 | ||| decoder invariant violation.
143 | |||
148 | assert_total $
149 | idris_crash
150 | "Data.String.Base64.Decode.readByte: input offset out of bounds"
154 | --------------------------------------------------------------------------------
155 | -- Mutable buffer access
156 | --------------------------------------------------------------------------------
158 | ||| Write a decoded byte to a mutable output buffer at a natural-number offset.
159 | |||
160 | ||| The destination buffer is scoped to the current `F1` region.
161 | |||
162 | ||| The decoder preallocates an output buffer large enough for the maximum
163 | ||| possible decoded result, so failure indicates an internal decoder
164 | ||| invariant violation.
165 | |||
170 | assert_total $
171 | idris_crash
172 | "Data.String.Base64.Decode.writeByte: output offset out of bounds"
176 | --------------------------------------------------------------------------------
177 | -- Four-byte hot path
178 | --------------------------------------------------------------------------------
180 | ||| Decode four Base64 alphabet bytes into a packed 24-bit output value.
181 | |||
182 | ||| The four input bytes are translated through the optimized `d0a`, `d1a`,
183 | ||| `d2a`, and `d3a` lookup tables.
184 | |||
185 | ||| Each table contains a pre-shifted contribution to the final 24-bit value.
186 | ||| Combining those contributions with bitwise OR reconstructs the original
187 | ||| three bytes.
188 | |||
189 | ||| Returns `Nothing` if any input byte is not a valid Base64 alphabet
190 | ||| character.
191 | |||
202 | in
208 | Nothing
212 | ||| Decode four complete, non-padded Base64 characters and write the resulting
213 | ||| three bytes to a mutable output buffer.
214 | |||
215 | ||| The optimized decoder tables reconstruct the original three bytes in
216 | ||| little-endian byte order within the packed 24-bit value:
217 | |||
218 | ||| bits 0..7 = first decoded byte
219 | ||| bits 8..15 = second decoded byte
220 | ||| bits 16..23 = third decoded byte
221 | |||
222 | ||| The destination buffer must contain at least three writable bytes beginning
223 | ||| at `destOffset`.
224 | |||
240 | --------------------------------------------------------------------------------
241 | -- Final padded groups
242 | --------------------------------------------------------------------------------
244 | ||| Decode a final Base64 quartet containing `==` padding.
245 | |||
246 | ||| A quartet of the form:
247 | |||
248 | ||| xx==
249 | |||
250 | ||| represents exactly one decoded byte.
251 | |||
252 | ||| The first two characters must be valid Base64 alphabet characters.
253 | |||
254 | ||| The optimized decoder tables place the decoded byte in the least
255 | ||| significant eight bits of the packed value.
256 | |||
257 | ||| The returned `Maybe Nat` contains the number of bytes written.
258 | |||
262 | then
264 | else
269 | in
272 | then
274 | else T1.do
284 | ||| Decode a final Base64 quartet containing `=` padding.
285 | |||
286 | ||| A quartet of the form:
287 | |||
288 | ||| xxx=
289 | |||
290 | ||| represents exactly two decoded bytes.
291 | |||
292 | ||| The first three characters must be valid Base64 alphabet characters.
293 | |||
294 | ||| The optimized decoder tables place the first decoded byte in bits 0..7
295 | ||| and the second decoded byte in bits 8..15 of the packed value.
296 | |||
297 | ||| The returned `Maybe Nat` contains the number of bytes written.
298 | |||
302 | then
304 | else
311 | in
315 | then
317 | else T1.do
331 | --------------------------------------------------------------------------------
332 | -- Native decoder
333 | --------------------------------------------------------------------------------
335 | ||| Decode an immutable Base64 input buffer into a preallocated mutable output
336 | ||| buffer.
337 | |||
338 | ||| Complete four-character Base64 groups are decoded through the optimized
339 | ||| `d0a`, `d1a`, `d2a`, and `d3a` lookup tables.
340 | |||
341 | ||| Padding is permitted only in the final quartet.
342 | |||
343 | ||| A final `==` quartet produces one byte, while a final `=` quartet produces
344 | ||| two bytes.
345 | |||
346 | ||| Mutation remains entirely inside the `F1` region associated with the
347 | ||| supplied destination buffer.
348 | |||
349 | ||| The returned `Maybe Nat` contains the number of decoded bytes written to
350 | ||| the destination buffer.
351 | |||
352 | decodeBuffer : {destLen : Nat} -> {srcLen : Nat} -> IBuffer srcLen -> MBuffer s destLen -> Nat -> F1 s (Maybe Nat)
355 | where
361 | in
363 | then
366 | then
367 | -- Base64 input must consist entirely of complete four-character
368 | -- quartets.
370 | else
383 | in
385 | then
386 | -- Padding cannot appear as the first character of a quartet.
389 | then
390 | -- Padding cannot appear as the second character of a quartet.
393 | then
394 | -- The only valid padded form here is:
395 | --
396 | -- xx==
397 | --
399 | then
400 | -- Padding must terminate the entire input.
402 | then T1.do
408 | pure $
409 | Just
411 | else
413 | else
416 | then
417 | -- The only valid padded form here is:
418 | --
419 | -- xxx=
420 | --
421 | -- Padding must terminate the entire input.
423 | then T1.do
429 | pure $
430 | Just
432 | else
434 | else
435 | -- Complete, non-padded quartet.
441 | go
445 | --------------------------------------------------------------------------------
446 | -- Immutable buffer decoding
447 | --------------------------------------------------------------------------------
449 | ||| Decode an immutable Base64 byte buffer.
450 | |||
451 | ||| A mutable destination buffer large enough for the maximum decoded result
452 | ||| is allocated inside an `F1` region.
453 | |||
454 | ||| After successful decoding, only the prefix actually written is frozen.
455 | ||| `unsafeFreezeLTE` performs this operation without copying the backing
456 | ||| buffer.
457 | |||
458 | ||| The resulting immutable byte buffer is then materialized as a
459 | ||| `List Bits8`.
460 | |||
466 | in
467 | run1 $ T1.do
476 | -- The decoder must never be able to write more than the
477 | -- precomputed maximum decoded size.
481 | pure $
484 | --------------------------------------------------------------------------------
485 | -- Public decoding API
486 | --------------------------------------------------------------------------------
488 | ||| Decode a list of Base64 bytes into its original bytes.
489 | |||
490 | ||| Returns `Nothing` if the input is not valid Base64.
491 | |||
492 | ||| The source list is copied once into an immutable `IBuffer`, after which
493 | ||| decoding operates entirely on the array library's indexed buffer types.
494 | |||
495 | ||| The decoder allocates a mutable output buffer inside an `F1` region,
496 | ||| processes complete quartets using the optimized `d0a` through `d3a`
497 | ||| lookup tables, freezes only the successfully written output prefix, and
498 | ||| finally converts that prefix to `List Bits8`.
499 | |||
500 | export
505 | ||| Decode a Base64 String into its original bytes.
506 | |||
507 | ||| The String is converted directly to an immutable UTF-8 byte buffer using
508 | ||| the array library's `fromString` operation.
509 | |||
510 | ||| Since Base64 is ASCII-only, valid Base64 text has the same byte
511 | ||| representation in UTF-8. Any non-Base64 UTF-8 byte is rejected by the
512 | ||| optimized decoder tables.
513 | |||
514 | ||| Returns `Nothing` when the input is not valid Base64.
515 | |||
516 | export