Skip to content

Commit 7c17a5c

Browse files
committed
CGDMF-141: Implement remaining algo's
1 parent a8563d0 commit 7c17a5c

2 files changed

Lines changed: 132 additions & 36 deletions

File tree

src/main/kotlin/api/wopi/services/WopiDocumentService.kt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,7 @@ import kotlin.time.Clock
2828
* as defined by the WOPI protocol. Delegates general EIO reads and writes to
2929
* [EnkelvoudigInformatieObjectService].
3030
*/
31-
class WopiDocumentService(
32-
private val eioService: EnkelvoudigInformatieObjectService,
33-
private val storageService: StorageService
34-
) {
31+
class WopiDocumentService(private val eioService: EnkelvoudigInformatieObjectService, private val storageService: StorageService) {
3532

3633
/**
3734
* Locks a file for a WOPI client using [wopiClientLock] as the lock token.

src/main/kotlin/services/IntegrityCalculationService.kt

Lines changed: 131 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,37 +8,124 @@ import java.security.DigestInputStream
88
import java.security.MessageDigest
99
import java.util.zip.CRC32
1010
import java.util.zip.CheckedInputStream
11+
import javax.crypto.Mac
12+
import javax.crypto.spec.SecretKeySpec
1113

1214
class IntegrityCalculationService {
1315

1416
companion object {
1517

16-
fun calculateIntegrity(data: ByteArray, algorithm: String?): IntegrityCalculationResult {
17-
if (algorithm.isNullOrEmpty()) return IntegrityCalculationResult("", "")
18-
val integriteitAlgoritme = IntegriteitAlgoritme.valueOf(algorithm)
19-
return when (integriteitAlgoritme) {
20-
IntegriteitAlgoritme.CRC_32 -> {
21-
val crc32 = CRC32()
22-
crc32.update(data)
23-
IntegrityCalculationResult(crc32.value.toString(16), algorithm)
24-
}
18+
// ── CRC-16/IBM (poly 0xA001, reflected) ─────────────────────────────
19+
private val CRC16_TABLE: IntArray = IntArray(256) { i ->
20+
var crc = i
21+
repeat(8) { crc = if (crc and 1 != 0) (crc ushr 1) xor 0xA001 else crc ushr 1 }
22+
crc
23+
}
2524

26-
IntegriteitAlgoritme.MD5,
27-
IntegriteitAlgoritme.SHA_1,
28-
IntegriteitAlgoritme.SHA_256,
29-
-> {
30-
val javaAlgo = when (integriteitAlgoritme) {
31-
IntegriteitAlgoritme.MD5 -> "MD5"
32-
IntegriteitAlgoritme.SHA_1 -> "SHA-1"
33-
else -> "SHA-256"
34-
}
35-
val hash = MessageDigest.getInstance(javaAlgo).digest(data)
36-
.joinToString("") { "%02x".format(it) }
37-
IntegrityCalculationResult(hash, algorithm)
38-
}
25+
private fun crc16(data: ByteArray): Int {
26+
var crc = 0
27+
for (b in data) crc = (crc ushr 8) xor CRC16_TABLE[(crc xor b.toInt()) and 0xFF]
28+
return crc
29+
}
30+
31+
// ── CRC-64/ECMA-182 (poly 0xC96C5795D7870F42) ───────────────────────
32+
private val CRC64_TABLE: LongArray = LongArray(256) { i ->
33+
var crc = i.toLong()
34+
repeat(8) { crc = if (crc and 1L != 0L) (crc ushr 1) xor -0x3693a86a2777a96bL else crc ushr 1 }
35+
crc
36+
}
37+
38+
private fun crc64(data: ByteArray): Long {
39+
var crc = -1L
40+
for (b in data) crc = (crc ushr 8) xor CRC64_TABLE[((crc xor b.toLong()) and 0xFF).toInt()]
41+
return crc.inv()
42+
}
3943

40-
else -> IntegrityCalculationResult("", "")
44+
// ── Fletcher checksums ───────────────────────────────────────────────
45+
private fun fletcher4(data: ByteArray): Int {
46+
var s1 = 0
47+
var s2 = 0
48+
for (b in data) {
49+
s1 = (s1 + (b.toInt() and 0x03)) % 15
50+
s2 = (s2 + s1) % 15
4151
}
52+
return (s2 shl 4) or s1
53+
}
54+
55+
private fun fletcher8(data: ByteArray): Int {
56+
var s1 = 0
57+
var s2 = 0
58+
for (b in data) {
59+
s1 = (s1 + (b.toInt() and 0xFF)) % 255
60+
s2 = (s2 + s1) % 255
61+
}
62+
return (s2 shl 8) or s1
63+
}
64+
65+
private fun fletcher16(data: ByteArray): Int {
66+
var s1 = 0
67+
var s2 = 0
68+
for (b in data) {
69+
s1 = (s1 + (b.toInt() and 0xFF)) % 65535
70+
s2 = (s2 + s1) % 65535
71+
}
72+
return (s2 shl 16) or s1
73+
}
74+
75+
private fun fletcher32(data: ByteArray): Long {
76+
var s1 = 0L
77+
var s2 = 0L
78+
for (b in data) {
79+
s1 = (s1 + (b.toLong() and 0xFF)) % 4294967295L
80+
s2 = (s2 + s1) % 4294967295L
81+
}
82+
return (s2 shl 32) or s1
83+
}
84+
85+
// ── HMAC-SHA256 with empty key (integrity without shared secret) ─────
86+
private fun hmacSha256(data: ByteArray): String {
87+
val mac = Mac.getInstance("HmacSHA256")
88+
mac.init(SecretKeySpec(ByteArray(0), "HmacSHA256"))
89+
return mac.doFinal(data).joinToString("") { "%02x".format(it) }
90+
}
91+
92+
// ── Shared helpers ───────────────────────────────────────────────────
93+
private fun hashBytesWithMessageDigest(data: ByteArray, algo: IntegriteitAlgoritme): String {
94+
val javaAlgo = algo.toJavaMessageDigestName()
95+
val hash = MessageDigest.getInstance(javaAlgo).digest(data)
96+
return hash.joinToString("") { "%02x".format(it) }
97+
}
98+
99+
private fun IntegriteitAlgoritme.toJavaMessageDigestName(): String = when (this) {
100+
IntegriteitAlgoritme.MD5 -> "MD5"
101+
IntegriteitAlgoritme.SHA_1 -> "SHA-1"
102+
IntegriteitAlgoritme.SHA_256 -> "SHA-256"
103+
else -> throw IllegalArgumentException("Not a MessageDigest algorithm: $this")
104+
}
105+
106+
private fun computeHash(data: ByteArray, algo: IntegriteitAlgoritme): String = when (algo) {
107+
IntegriteitAlgoritme.CRC_16 -> crc16(data).toString(16)
108+
IntegriteitAlgoritme.CRC_32 -> {
109+
val crc32 = CRC32()
110+
crc32.update(data)
111+
crc32.value.toString(16)
112+
}
113+
IntegriteitAlgoritme.CRC_64 -> crc64(data).toULong().toString(16)
114+
IntegriteitAlgoritme.FLETCHER_4 -> fletcher4(data).toString(16)
115+
IntegriteitAlgoritme.FLETCHER_8 -> fletcher8(data).toString(16)
116+
IntegriteitAlgoritme.FLETCHER_16 -> fletcher16(data).toString(16)
117+
IntegriteitAlgoritme.FLETCHER_32 -> fletcher32(data).toString(16)
118+
IntegriteitAlgoritme.HMAC -> hmacSha256(data)
119+
IntegriteitAlgoritme.MD5,
120+
IntegriteitAlgoritme.SHA_1,
121+
IntegriteitAlgoritme.SHA_256,
122+
-> hashBytesWithMessageDigest(data, algo)
123+
}
124+
125+
fun calculateIntegrity(data: ByteArray, algorithm: String?): IntegrityCalculationResult {
126+
if (algorithm.isNullOrEmpty()) return IntegrityCalculationResult("", "")
127+
val algo = IntegriteitAlgoritme.valueOf(algorithm)
128+
return IntegrityCalculationResult(computeHash(data, algo), algorithm)
42129
}
43130

44131
/**
@@ -50,13 +137,18 @@ class IntegrityCalculationService {
50137
*
51138
* When [algorithm] is null or blank [block] is called with the original [stream] and
52139
* empty strings are returned, so the upload still proceeds normally.
140+
*
141+
* Note: CRC_16, CRC_64, Fletcher variants, and HMAC do not have standard Java streaming
142+
* filter support. For those algorithms the stream bytes are accumulated and hashed after
143+
* [block] completes. All MessageDigest-based algorithms (MD5, SHA_1, SHA_256) and CRC_32
144+
* use true streaming filters.
53145
*/
54146
fun <T> withIntegrity(stream: InputStream, algorithm: String?, block: (InputStream) -> T): Pair<T, IntegrityCalculationResult> {
55147
if (algorithm.isNullOrEmpty()) {
56148
return block(stream) to IntegrityCalculationResult("", "")
57149
}
58-
val integriteitAlgoritme = IntegriteitAlgoritme.valueOf(algorithm)
59-
return when (integriteitAlgoritme) {
150+
val algo = IntegriteitAlgoritme.valueOf(algorithm)
151+
return when (algo) {
60152
IntegriteitAlgoritme.CRC_32 -> {
61153
val crc32 = CRC32()
62154
val cis = CheckedInputStream(stream, crc32)
@@ -68,19 +160,26 @@ class IntegrityCalculationService {
68160
IntegriteitAlgoritme.SHA_1,
69161
IntegriteitAlgoritme.SHA_256,
70162
-> {
71-
val javaAlgo = when (integriteitAlgoritme) {
72-
IntegriteitAlgoritme.MD5 -> "MD5"
73-
IntegriteitAlgoritme.SHA_1 -> "SHA-1"
74-
else -> "SHA-256"
75-
}
76-
val digest = MessageDigest.getInstance(javaAlgo)
163+
val digest = MessageDigest.getInstance(algo.toJavaMessageDigestName())
77164
val digestStream = DigestInputStream(stream, digest)
78165
val result = block(digestStream)
79166
val hash = digest.digest().joinToString("") { "%02x".format(it) }
80167
result to IntegrityCalculationResult(hash, algorithm)
81168
}
82169

83-
else -> block(stream) to IntegrityCalculationResult("", "")
170+
// No standard streaming filter — buffer and hash after block completes.
171+
IntegriteitAlgoritme.CRC_16,
172+
IntegriteitAlgoritme.CRC_64,
173+
IntegriteitAlgoritme.FLETCHER_4,
174+
IntegriteitAlgoritme.FLETCHER_8,
175+
IntegriteitAlgoritme.FLETCHER_16,
176+
IntegriteitAlgoritme.FLETCHER_32,
177+
IntegriteitAlgoritme.HMAC,
178+
-> {
179+
val buffer = stream.readAllBytes()
180+
val result = block(buffer.inputStream())
181+
result to IntegrityCalculationResult(computeHash(buffer, algo), algorithm)
182+
}
84183
}
85184
}
86185
}

0 commit comments

Comments
 (0)