Skip to content

Commit 4332319

Browse files
authored
feat: add wrapper for checksums + unit tests (#642)
1 parent 98018ec commit 4332319

File tree

2 files changed

+246
-0
lines changed

2 files changed

+246
-0
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0.
4+
*/
5+
6+
import AwsCommonRuntimeKit
7+
8+
enum HashResult {
9+
case data(Data)
10+
case integer(UInt32)
11+
}
12+
13+
enum HashError: Error {
14+
case invalidInput
15+
case hashingFailed(reason: String)
16+
}
17+
18+
enum HashFunction {
19+
case crc32, crc32c, sha1, sha256, md5
20+
21+
static func from(string: String) -> HashFunction? {
22+
switch string.lowercased() {
23+
case "crc32": return .crc32
24+
case "crc32c": return .crc32c
25+
case "sha1": return .sha1
26+
case "sha256": return .sha256
27+
case "md5": return .md5 // md5 is not a valid flexible checksum algorithm
28+
default: return nil
29+
}
30+
}
31+
32+
var isSupported: Bool {
33+
switch self {
34+
case .crc32, .crc32c, .sha256, .sha1:
35+
return true
36+
default:
37+
return false
38+
}
39+
}
40+
41+
func computeHash(of data: Data) throws -> HashResult {
42+
switch self {
43+
case .crc32:
44+
return .integer(data.computeCRC32())
45+
case .crc32c:
46+
return .integer(data.computeCRC32C())
47+
case .sha1:
48+
do {
49+
let hashed = try data.computeSHA1()
50+
return .data(hashed)
51+
} catch {
52+
throw HashError.hashingFailed(reason: "Error computing SHA1: \(error)")
53+
}
54+
case .sha256:
55+
do {
56+
let hashed = try data.computeSHA256()
57+
return .data(hashed)
58+
} catch {
59+
throw HashError.hashingFailed(reason: "Error computing SHA256: \(error)")
60+
}
61+
case .md5:
62+
do {
63+
let hashed = try data.computeMD5()
64+
return .data(hashed)
65+
} catch {
66+
throw HashError.hashingFailed(reason: "Error computing MD5: \(error)")
67+
}
68+
}
69+
}
70+
}
71+
72+
extension HashResult {
73+
74+
// Convert a HashResult to a hexadecimal String
75+
func toHexString() -> String {
76+
switch self {
77+
case .data(let data):
78+
return data.map { String(format: "%02x", $0) }.joined()
79+
case .integer(let integer):
80+
return String(format: "%08x", integer)
81+
}
82+
}
83+
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import XCTest
9+
@testable import ClientRuntime
10+
import AwsCommonRuntimeKit
11+
12+
class HashFunctionTests: XCTestCase {
13+
14+
override func setUp() {
15+
// Initialize function needs to be called before interacting with CRT
16+
CommonRuntimeKit.initialize()
17+
}
18+
19+
func testCRC32NonUTF8Bytes() {
20+
guard let hashFunction = HashFunction.from(string: "crc32") else {
21+
XCTFail("CRC32 not found")
22+
return
23+
}
24+
25+
// Create test data
26+
let testBytes: [UInt8] = [0xFF, 0xFE, 0xFD, 0xFC] // Bytes not valid in UTF-8
27+
let testData = Data(testBytes)
28+
29+
let computedHash = try? hashFunction.computeHash(of: testData)
30+
guard case let .integer(result)? = computedHash else {
31+
XCTFail("CRC32 computed hash is not an integer or is nil")
32+
return
33+
}
34+
let expected = UInt32(1426237168)
35+
XCTAssertEqual(result, expected, "CRC32 hash does not match expected value")
36+
}
37+
38+
func testCRC32CNonUTF8Bytes() {
39+
guard let hashFunction = HashFunction.from(string: "crc32c") else {
40+
XCTFail("CRC32C not found")
41+
return
42+
}
43+
44+
// Create test data
45+
let testBytes: [UInt8] = [0xFF, 0xFE, 0xFD, 0xFC] // Bytes not valid in UTF-8
46+
let testData = Data(testBytes)
47+
48+
let computedHash = try? hashFunction.computeHash(of: testData)
49+
guard case let .integer(result)? = computedHash else {
50+
XCTFail("CRC32C computed hash is not an integer or is nil")
51+
return
52+
}
53+
let expected = UInt32(1856745115)
54+
XCTAssertEqual(result, expected, "CRC32C hash does not match expected value")
55+
}
56+
57+
func testSHA1NonUTF8Bytes() {
58+
guard let hashFunction = HashFunction.from(string: "sha1") else {
59+
XCTFail("SHA1 not found")
60+
return
61+
}
62+
63+
// Create test data
64+
let testBytes: [UInt8] = [0xFF, 0xFE, 0xFD, 0xFC] // Bytes not valid in UTF-8
65+
let testData = Data(testBytes)
66+
67+
let computedHash = try? hashFunction.computeHash(of: testData)
68+
guard case let .data(result)? = computedHash else {
69+
XCTFail("SHA1 computed hash is not a data type or is nil")
70+
return
71+
}
72+
let expected = "ADfJtWg8Do2MpnFNsvFRmyMuEOI="
73+
XCTAssertEqual(result.base64EncodedString(), expected, "SHA1 hash does not match expected value")
74+
}
75+
76+
func testSHA256NonUTF8Bytes() {
77+
guard let hashFunction = HashFunction.from(string: "sha256") else {
78+
XCTFail("SHA256 not found")
79+
return
80+
}
81+
82+
// Create test data
83+
let testBytes: [UInt8] = [0xFF, 0xFE, 0xFD, 0xFC] // Bytes not valid in UTF-8
84+
let testData = Data(testBytes)
85+
86+
let computedHash = try? hashFunction.computeHash(of: testData)
87+
guard case let .data(result)? = computedHash else {
88+
XCTFail("SHA256 computed hash is not a data type or is nil")
89+
return
90+
}
91+
let expected = "jCosV0rEcc6HWQwT8O/bQr0ssZuxhJM3nUW/zJBgtlc="
92+
XCTAssertEqual(result.base64EncodedString(), expected, "SHA256 hash does not match expected value")
93+
}
94+
95+
func testMD5NonUTF8Bytes() {
96+
guard let hashFunction = HashFunction.from(string: "md5") else {
97+
XCTFail("MD5 not found")
98+
return
99+
}
100+
101+
// Create test data
102+
let testBytes: [UInt8] = [0xFF, 0xFE, 0xFD, 0xFC] // Bytes not valid in UTF-8
103+
let testData = Data(testBytes)
104+
105+
let computedHash = try? hashFunction.computeHash(of: testData)
106+
guard case let .data(result)? = computedHash else {
107+
XCTFail("MD5 computed hash is not a data type or is nil")
108+
return
109+
}
110+
let expected = "ilWq/WLcPzYHQ8fAzwCCLg=="
111+
XCTAssertEqual(result.base64EncodedString(), expected, "MD5 hash does not match expected value")
112+
}
113+
114+
func testInvalidHashFunction() {
115+
let invalidHashFunction = HashFunction.from(string: "invalid")
116+
XCTAssertNil(invalidHashFunction, "Invalid hash function should return nil")
117+
}
118+
119+
func testHashFunctionToHexString() {
120+
let testData = Data("Hello, world!".utf8)
121+
122+
// CRC32
123+
if let crc32Function = HashFunction.from(string: "crc32"),
124+
let crc32Result = try? crc32Function.computeHash(of: testData).toHexString() {
125+
XCTAssertEqual(crc32Result, "ebe6c6e6", "CRC32 hexadecimal representation does not match expected value")
126+
} else {
127+
XCTFail("CRC32 hash function not found or computation failed")
128+
}
129+
130+
// CRC32C
131+
if let crc32cFunction = HashFunction.from(string: "crc32c"),
132+
let crc32cResult = try? crc32cFunction.computeHash(of: testData).toHexString() {
133+
XCTAssertEqual(crc32cResult, "c8a106e5", "CRC32C hexadecimal representation does not match expected value")
134+
} else {
135+
XCTFail("CRC32C hash function not found or computation failed")
136+
}
137+
138+
// SHA1
139+
if let sha1Function = HashFunction.from(string: "sha1"),
140+
let sha1Result = try? sha1Function.computeHash(of: testData).toHexString() {
141+
XCTAssertEqual(sha1Result, "943a702d06f34599aee1f8da8ef9f7296031d699", "SHA1 hexadecimal representation does not match expected value")
142+
} else {
143+
XCTFail("SHA1 hash function not found or computation failed")
144+
}
145+
146+
// SHA256
147+
if let sha256Function = HashFunction.from(string: "sha256"),
148+
let sha256Result = try? sha256Function.computeHash(of: testData).toHexString() {
149+
XCTAssertEqual(sha256Result, "315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3", "SHA256 hexadecimal representation does not match expected value")
150+
} else {
151+
XCTFail("SHA256 hash function not found or computation failed")
152+
}
153+
154+
// MD5
155+
if let md5Function = HashFunction.from(string: "md5"),
156+
let md5Result = try? md5Function.computeHash(of: testData).toHexString() {
157+
XCTAssertEqual(md5Result, "6cd3556deb0da54bca060b4c39479839", "MD5 hexadecimal representation does not match expected value")
158+
} else {
159+
XCTFail("MD5 hash function not found or computation failed")
160+
}
161+
}
162+
163+
}

0 commit comments

Comments
 (0)