Skip to content

Commit d213ae0

Browse files
authored
make Base16 unsafe API slightly safer, add documentation (#4)
1 parent 3783866 commit d213ae0

File tree

5 files changed

+125
-45
lines changed

5 files changed

+125
-45
lines changed

README.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<div align="center">
22

3-
***`hash`***<br>`0.2.3`
3+
***`hash`***<br>`0.3.0`
44

55
[![ci status](https://github.com/kelvin13/swift-hash/actions/workflows/build.yml/badge.svg)](https://github.com/kelvin13/swift-hash/actions/workflows/build.yml)
66
[![ci status](https://github.com/kelvin13/swift-hash/actions/workflows/build-devices.yml/badge.svg)](https://github.com/kelvin13/swift-hash/actions/workflows/build-devices.yml)
@@ -12,4 +12,24 @@
1212

1313
</div>
1414

15-
*`swift-hash`* is an inline-only microframework providing generic, pure-Swift implementations of the [SHA-2](https://en.wikipedia.org/wiki/SHA-2) and HMAC-SHA-2 hashing functions.
15+
*`swift-hash`* is an inline-only microframework providing generic, pure-Swift implementations of various hashes, checksums, and binary utilities.
16+
17+
## products
18+
19+
The package vends the following library products:
20+
21+
1. [`Base16`](Sources/Base16)
22+
23+
Tools for encoding to and decoding from base-16 strings.
24+
25+
1. [`Base64`](Sources/Base64)
26+
27+
Tools for encoding to and decoding from base-64 strings.
28+
29+
1. [`CRC`](Sources/CRC)
30+
31+
Implements [CRC-32](https://en.wikipedia.org/wiki/Cyclic_redundancy_check) checksums.
32+
33+
1. [`SHA2`](Sources/SHA2)
34+
35+
Implements the [SHA-256](https://en.wikipedia.org/wiki/SHA-2) and HMAC-SHA-256 hashing functions.

Sources/Base16/Base16.swift

Lines changed: 60 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,31 @@
11
import BaseDigits
22

3+
/// A namespace for base-16 utilities.
34
public
4-
enum Base16
5+
enum Base16
56
{
7+
/// Decodes some ``String``-like type containing an ASCII-encoded base-16 string
8+
/// to some ``RangeReplaceableCollection`` type. The order of the decoded bytes
9+
/// in the output matches the order of the (pairs of) hexadecimal digits in the
10+
/// input string.
11+
///
12+
/// Characters (including UTF-8 continuation bytes) that are not base-16 digits
13+
/// will be interpreted as zeros. If the string does not contain an even number
14+
/// of digits, the trailing digit will be ignored.
615
@inlinable public static
716
func decode<ASCII, Bytes>(_ ascii:ASCII, to _:Bytes.Type = Bytes.self) -> Bytes
817
where Bytes:RangeReplaceableCollection, Bytes.Element == UInt8,
918
ASCII:StringProtocol
1019
{
1120
self.decode(ascii.utf8, to: Bytes.self)
1221
}
22+
/// Decodes an ASCII-encoded base-16 string to some ``RangeReplaceableCollection`` type.
23+
/// The order of the decoded bytes in the output matches the order of the (pairs of)
24+
/// hexadecimal digits in the input.
25+
///
26+
/// Characters (including UTF-8 continuation bytes) that are not base-16 digits
27+
/// will be interpreted as zeros. If the input does not yield an even number of
28+
/// digits, the trailing digit will be ignored.
1329
@inlinable public static
1430
func decode<ASCII, Bytes>(_ ascii:ASCII, to _:Bytes.Type = Bytes.self) -> Bytes
1531
where Bytes:RangeReplaceableCollection, Bytes.Element == UInt8,
@@ -25,7 +41,7 @@ enum Base16
2541
}
2642
return bytes
2743
}
28-
44+
/// Encodes a sequence of bytes to a base-16 string with the specified lettercasing.
2945
@inlinable public static
3046
func encode<Bytes, Digits>(_ bytes:Bytes, with _:Digits.Type) -> String
3147
where Bytes:Sequence, Bytes.Element == UInt8, Digits:BaseDigits
@@ -42,6 +58,12 @@ enum Base16
4258
}
4359
extension Base16
4460
{
61+
/// Decodes an ASCII-encoded base-16 string into a pre-allocated buffer,
62+
/// returning [`nil`]() if the input did not yield enough bytes to fill
63+
/// the buffer completely.
64+
///
65+
/// Characters (including UTF-8 continuation bytes) that are not base-16 digits
66+
/// will be interpreted as zeros.
4567
@inlinable public static
4668
func decode<ASCII>(_ ascii:ASCII,
4769
into bytes:UnsafeMutableRawBufferPointer) -> Void?
@@ -62,6 +84,13 @@ extension Base16
6284
}
6385
return ()
6486
}
87+
/// Encodes a sequence of bytes into a pre-allocated buffer as a base-16
88+
/// string with the specified lettercasing.
89+
///
90+
/// The size of the `ascii` buffer must be exactly twice the inline size
91+
/// of `words`. If this method is used incorrectly, the output buffer may
92+
/// be incompletely initialized, but it will never write to memory outside
93+
/// of the buffer’s bounds.
6594
@inlinable public static
6695
func encode<BigEndian, Digits>(storing words:BigEndian,
6796
into ascii:UnsafeMutableRawBufferPointer,
@@ -72,21 +101,22 @@ extension Base16
72101
{
73102
assert(2 * $0.count <= ascii.count)
74103

75-
var offset:Int = ascii.startIndex
76-
for byte:UInt8 in $0
104+
for (offset, byte):(Int, UInt8)
105+
in zip(stride(from: ascii.startIndex, to: ascii.endIndex, by: 2), $0)
77106
{
78-
ascii[offset] = Digits[byte >> 4]
79-
ascii.formIndex(after: &offset)
80-
ascii[offset] = Digits[byte & 0x0f]
81-
ascii.formIndex(after: &offset)
107+
ascii[offset ] = Digits[byte >> 4]
108+
ascii[offset + 1] = Digits[byte & 0x0f]
82109
}
83110
}
84111
}
85112
}
86113
extension Base16
87-
{
114+
{
88115
#if swift(>=5.6)
89-
@inlinable public static
116+
/// Decodes an ASCII-encoded base-16 string to some (usually trivial) type.
117+
/// This is essentially the same as loading values from raw memory, so this
118+
/// method should only be used to load trivial types.
119+
@inlinable public static
90120
func decode<ASCII, BigEndian>(_ ascii:ASCII,
91121
loading _:BigEndian.Type = BigEndian.self) -> BigEndian?
92122
where ASCII:Sequence, ASCII.Element == UInt8
@@ -116,13 +146,19 @@ extension Base16
116146
}
117147
#endif
118148

119-
149+
/// Encodes the raw bytes of the given value to a base-16 string with the
150+
/// specified lettercasing. The bytes with the lowest addresses appear first
151+
/// in the encoded output.
152+
///
153+
/// This method is slightly faster than calling ``encode(_:with:)`` on an
154+
/// unsafe buffer-pointer view of `words`.
120155
@inlinable public static
121156
func encode<BigEndian, Digits>(storing words:BigEndian,
122157
with _:Digits.Type) -> String
123158
where Digits:BaseDigits
124159
{
125160
let bytes:Int = 2 * MemoryLayout<BigEndian>.size
161+
126162
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
127163
if #available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 14.0, *)
128164
{
@@ -134,39 +170,26 @@ extension Base16
134170
return bytes
135171
}
136172
}
137-
else
138-
{
139-
return .init(
140-
decoding: try Self.encode(storing: words, to: [UInt8].self, with: Digits.self),
141-
as: Unicode.UTF8.self)
142-
}
143-
#elseif swift(>=5.4)
144-
return .init(unsafeUninitializedCapacity: bytes)
145-
{
146-
Self.encode(storing: words,
147-
into: UnsafeMutableRawBufferPointer.init($0),
148-
with: Digits.self)
149-
return bytes
150-
}
151-
#else
173+
#endif
174+
175+
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || swift(<5.4)
152176
return .init(
153-
decoding: try Self.encode(storing: words, to: [UInt8].self, with: Digits.self),
177+
decoding: [UInt8].init(unsafeUninitializedCapacity: bytes)
178+
{
179+
Self.encode(storing: words,
180+
into: UnsafeMutableRawBufferPointer.init($0),
181+
with: Digits.self)
182+
$1 = bytes
183+
},
154184
as: Unicode.UTF8.self)
155-
#endif
156-
}
157-
158-
@inlinable public static
159-
func encode<BigEndian, Digits>(storing words:BigEndian, to _:[UInt8].Type,
160-
with _:Digits.Type) -> [UInt8]
161-
where Digits:BaseDigits
162-
{
163-
let bytes:Int = 2 * MemoryLayout<BigEndian>.size
185+
#else
164186
return .init(unsafeUninitializedCapacity: bytes)
165187
{
166188
Self.encode(storing: words,
167189
into: UnsafeMutableRawBufferPointer.init($0),
168190
with: Digits.self)
169-
$1 = bytes
191+
return bytes
170192
}
193+
#endif
171194
}
172195
}

Sources/Base64/Base64.Input.swift

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
extension Base64
22
{
3-
/// An abstraction over text input, which discards ASCII whitespace
4-
/// characters.
3+
/// An abstraction over text input, which discards the ASCII whitespace
4+
/// characters [`'\t'`](), [`'\n'`](), [`'\f'`](), [`'\r'`](), and [`' '`]().
5+
///
6+
/// Iteration over an instance of this type will halt upon encountering the
7+
/// first [`'='`]() padding character, even if the underlying sequence contains
8+
/// more characters.
59
@frozen public
6-
struct Input<UTF8> where UTF8:Sequence, UTF8.Element == UInt8
10+
struct Input<ASCII> where ASCII:Sequence, ASCII.Element == UInt8
711
{
812
public
9-
var iterator:UTF8.Iterator
13+
var iterator:ASCII.Iterator
1014

1115
@inlinable public
12-
init(_ utf8:UTF8)
16+
init(_ ascii:ASCII)
1317
{
14-
self.iterator = utf8.makeIterator()
18+
self.iterator = ascii.makeIterator()
1519
}
1620
}
1721
}

Sources/Base64/Base64.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,45 @@
11
import BaseDigits
22

3+
/// A namespace for base-64 utilities.
4+
///
5+
/// The interface is superficially similar to that of the ``/Base16`` module,
6+
/// but the decoding methods are slightly more lenient in their inputs, as
7+
/// they ignore whitespace and newlines.
38
public
49
enum Base64
510
{
11+
/// Decodes some ``String``-like type containing an ASCII-encoded base-64 string
12+
/// to some ``RangeReplaceableCollection`` type, skipping over any ASCII
13+
/// whitespace characters. Padding is not required.
14+
///
15+
/// Characters (including UTF-8 continuation bytes) that are neither base-64 digits
16+
/// nor ASCII whitespace characters will be interpreted as zeros.
17+
///
18+
/// See ``Base64/Input`` for a list of recognized ASCII whitespace characters.
19+
///
20+
/// > Important:
21+
/// Unicode whitespace characters, such as non-breaking spaces, will *not*
22+
/// be skipped, and their constituent UTF-8 code units will be interpreted
23+
/// as zeros.
624
@inlinable public static
725
func decode<ASCII, Bytes>(_ ascii:ASCII, to _:Bytes.Type = Bytes.self) -> Bytes
826
where Bytes:RangeReplaceableCollection, Bytes.Element == UInt8,
927
ASCII:StringProtocol
1028
{
1129
self.decode(ascii.utf8, to: Bytes.self)
1230
}
31+
/// Decodes an ASCII-encoded base-64 string to some ``RangeReplaceableCollection`` type,
32+
/// skipping over any ASCII whitespace characters. Padding is not required.
33+
///
34+
/// Characters (including UTF-8 continuation bytes) that are neither base-64 digits
35+
/// nor ASCII whitespace characters will be interpreted as zeros.
36+
///
37+
/// See ``Base64/Input`` for a list of recognized ASCII whitespace characters.
38+
///
39+
/// > Important:
40+
/// Unicode whitespace characters, such as non-breaking spaces, will *not*
41+
/// be skipped, and their constituent UTF-8 code units will be interpreted
42+
/// as zeros.
1343
@inlinable public static
1444
func decode<ASCII, Bytes>(_ ascii:ASCII, to _:Bytes.Type = Bytes.self) -> Bytes
1545
where Bytes:RangeReplaceableCollection, Bytes.Element == UInt8,
@@ -43,6 +73,7 @@ enum Base64
4373
return bytes
4474
}
4575

76+
/// Encodes a sequence of bytes to a base-64 string with padding if needed.
4677
@inlinable public static
4778
func encode<Bytes>(_ bytes:Bytes) -> String where Bytes:Sequence, Bytes.Element == UInt8
4879
{

Sources/BaseDigits/BaseDigits.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ protocol BaseDigits
77
}
88
extension BaseDigits
99
{
10+
/// Gets the ASCII value for the given remainder as a ``Unicode/Scalar``.
1011
@inlinable public static
1112
subscript(remainder:UInt8, as _:Unicode.Scalar.Type = Unicode.Scalar.self) -> Unicode.Scalar
1213
{
1314
.init(Self[remainder])
1415
}
16+
/// Gets the ASCII value for the given remainder as a ``Character``.
1517
@inlinable public static
1618
subscript(remainder:UInt8, as _:Character.Type = Character.self) -> Character
1719
{

0 commit comments

Comments
 (0)