Skip to content

Commit 3b38019

Browse files
committed
refactor: streamline CRC implementations by introducing a unified CrcEngine for consistency across CRC8, CRC16, and CRC32 classes; simplify test structure to enhance coverage and clarity
1 parent 7287cc9 commit 3b38019

File tree

2 files changed

+115
-192
lines changed

2 files changed

+115
-192
lines changed

src/main/kotlin/com/eignex/kencode/Crc.kt

Lines changed: 86 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,76 @@
11
package com.eignex.kencode
22

3+
/**
4+
* Shared CRC digest engine parameterized by width (up to 32 bits).
5+
* Uses Long arithmetic throughout to handle both 16-bit and 32-bit cases uniformly.
6+
*/
7+
private class CrcEngine(
8+
poly: Int,
9+
init: Int,
10+
private val refin: Boolean,
11+
private val refout: Boolean,
12+
xorOut: Int,
13+
private val width: Int
14+
) {
15+
private val mask: Long = if (width == 32) 0xFFFFFFFFL else (1L shl width) - 1L
16+
private val refPoly: Long = poly.reverseBits(width).toLong() and mask
17+
private val refInit: Long = init.reverseBits(width).toLong() and mask
18+
private val xorOutL: Long = xorOut.toLong() and mask
19+
20+
val size: Int = (width + 7) / 8
21+
22+
fun digest(data: ByteArray): ByteArray {
23+
var crc = refInit
24+
for (b in data) {
25+
val raw = b.toInt() and 0xFF
26+
val byte = if (refin) raw else raw.reverseBits8()
27+
crc = crc xor byte.toLong()
28+
repeat(8) {
29+
crc = if ((crc and 1L) != 0L) (crc ushr 1) xor refPoly else crc ushr 1
30+
}
31+
crc = crc and mask
32+
}
33+
var finalCrc = if (refout) crc else crc.toInt().reverseBits(width).toLong() and mask
34+
finalCrc = (finalCrc xor xorOutL) and mask
35+
return ByteArray(size) { i ->
36+
val shift = 8 * (size - 1 - i)
37+
((finalCrc ushr shift) and 0xFFL).toByte()
38+
}
39+
}
40+
}
41+
42+
/**
43+
* CRC-8 implementation with configurable polynomial and parameters.
44+
*
45+
* Default configuration implements CRC-8/SMBUS.
46+
*
47+
* Parameters:
48+
* @param poly generator polynomial (high bit implicit).
49+
* @param init initial CRC register value.
50+
* @param refin whether to reflect bits of each input byte.
51+
* @param refout whether to reflect the final CRC value.
52+
* @param xorOut final XOR mask.
53+
*
54+
* Output:
55+
* - Returned as a single-byte array.
56+
*/
57+
open class Crc8(
58+
poly: Int = 0x07,
59+
init: Int = 0x00,
60+
refin: Boolean = false,
61+
refout: Boolean = false,
62+
xorOut: Int = 0x00
63+
) : Checksum {
64+
65+
companion object Default : Crc8()
66+
67+
private val engine = CrcEngine(poly, init, refin, refout, xorOut, width = 8)
68+
69+
override val size: Int get() = engine.size
70+
71+
override fun digest(data: ByteArray): ByteArray = engine.digest(data)
72+
}
73+
374
/**
475
* CRC-16 implementation with configurable polynomial and parameters.
576
*
@@ -20,54 +91,19 @@ package com.eignex.kencode
2091
open class Crc16(
2192
poly: Int = 0x1021,
2293
init: Int = 0xFFFF,
23-
private val refin: Boolean = true,
24-
private val refout: Boolean = true,
25-
private val xorOut: Int = 0xFFFF,
26-
private val width: Int = 16
94+
refin: Boolean = true,
95+
refout: Boolean = true,
96+
xorOut: Int = 0xFFFF,
97+
width: Int = 16
2798
) : Checksum {
2899

29100
companion object Default : Crc16()
30101

31-
private val mask = (1 shl width) - 1
32-
33-
// Internal representation is reflected (LSB-first),
34-
// so pre-reflect poly and init.
35-
private val refPoly: Int = poly.reverseBits(width)
36-
private val refInit: Int = init.reverseBits(width)
37-
38-
override val size: Int = (width + 7) / 8
39-
40-
override fun digest(data: ByteArray): ByteArray {
41-
var crc = refInit and mask
42-
43-
for (i in data.indices) {
44-
val raw = data[i].toInt() and 0xFF
45-
val b = if (refin) raw else raw.reverseBits8()
46-
47-
crc = crc xor b
48-
repeat(8) {
49-
crc = if ((crc and 1) != 0) {
50-
(crc ushr 1) xor refPoly
51-
} else {
52-
crc ushr 1
53-
}
54-
}
55-
crc = crc and mask
56-
}
57-
58-
// Convert from internal reflected form to requested output form.
59-
crc = if (refout) crc else crc.reverseBits(width)
102+
private val engine = CrcEngine(poly, init, refin, refout, xorOut, width)
60103

61-
crc = (crc xor xorOut) and mask
104+
override val size: Int get() = engine.size
62105

63-
// Output big-endian (high byte first).
64-
val out = ByteArray(size)
65-
for (i in 0 until size) {
66-
val shift = 8 * (size - 1 - i)
67-
out[i] = ((crc ushr shift) and 0xFF).toByte()
68-
}
69-
return out
70-
}
106+
override fun digest(data: ByteArray): ByteArray = engine.digest(data)
71107
}
72108

73109
/**
@@ -88,58 +124,21 @@ open class Crc16(
88124
* - Internal representation uses reflected form.
89125
*/
90126
open class Crc32(
91-
poly: Int = 0x04C11DB7, // canonical CRC-32 poly
127+
poly: Int = 0x04C11DB7,
92128
init: Int = 0xFFFFFFFF.toInt(),
93-
private val refin: Boolean = true,
94-
private val refout: Boolean = true,
95-
private val xorOut: Int = 0xFFFFFFFF.toInt(),
96-
private val width: Int = 32
129+
refin: Boolean = true,
130+
refout: Boolean = true,
131+
xorOut: Int = 0xFFFFFFFF.toInt(),
132+
width: Int = 32
97133
) : Checksum {
98134

99135
companion object Default : Crc32()
100136

101-
private val mask: Long = if (width == 32) 0xFFFFFFFFL
102-
else (1L shl width) - 1L
103-
104-
// Internal representation: reflected
105-
private val refPoly: Long = poly.reverseBits(width).toLong() and mask
106-
private val refInit: Long = init.reverseBits(width).toLong() and mask
107-
108-
override val size: Int = (width + 7) / 8
109-
110-
override fun digest(data: ByteArray): ByteArray {
111-
var crc = refInit
112-
113-
for (i in data.indices) {
114-
val raw = data[i].toInt() and 0xFF
115-
val b = if (refin) raw else raw.reverseBits8()
116-
117-
crc = crc xor (b.toLong() and 0xFFL)
118-
repeat(8) {
119-
crc = if ((crc and 1L) != 0L) {
120-
(crc ushr 1) xor refPoly
121-
} else {
122-
crc ushr 1
123-
}
124-
}
125-
crc = crc and mask
126-
}
127-
128-
var finalCrc = if (refout) {
129-
crc
130-
} else {
131-
crc.toInt().reverseBits(width).toLong() and mask
132-
}
137+
private val engine = CrcEngine(poly, init, refin, refout, xorOut, width)
133138

134-
finalCrc = (finalCrc xor (xorOut.toLong() and mask)) and mask
139+
override val size: Int get() = engine.size
135140

136-
val out = ByteArray(size)
137-
for (i in 0 until size) {
138-
val shift = 8 * (size - 1 - i)
139-
out[i] = ((finalCrc ushr shift) and 0xFFL).toByte()
140-
}
141-
return out
142-
}
141+
override fun digest(data: ByteArray): ByteArray = engine.digest(data)
143142
}
144143

145144
/** Bit helpers */

src/test/kotlin/com/eignex/kencode/CrcTest.kt

Lines changed: 29 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -2,125 +2,49 @@ package com.eignex.kencode
22

33
import kotlin.test.*
44

5-
class Crc16Test {
6-
private val crc16 = Crc16() // defaults = CRC-16/X25
7-
8-
private fun digest(s: String): ByteArray =
9-
crc16.digest(s.toByteArray())
10-
11-
private fun Int.toBytesBigEndian(size: Int): ByteArray {
12-
val out = ByteArray(size)
13-
for (i in 0 until size) {
14-
val shift = 8 * (size - 1 - i)
15-
out[i] = ((this ushr shift) and 0xFF).toByte()
16-
}
17-
return out
18-
}
5+
class CrcTest {
196

20-
@Test
21-
fun `crc16 x25 check value 123456789`() {
22-
val crc = digest("123456789")
23-
val expected = 0x906E.toBytesBigEndian(2)
24-
assertContentEquals(expected, crc)
25-
}
7+
private fun hex(s: String): ByteArray =
8+
ByteArray(s.length / 2) { i -> s.substring(i * 2, i * 2 + 2).toInt(16).toByte() }
269

27-
@Test
28-
fun `crc16 x25 empty string`() {
29-
// X25 params: init=0xFFFF, xorOut=0xFFFF, refin+refout => empty -> 0x0000
30-
val crc = digest("")
31-
val expected = 0x0000.toBytesBigEndian(2)
32-
assertContentEquals(expected, crc)
33-
}
10+
private fun Checksum.digest(s: String): ByteArray = digest(s.toByteArray())
3411

35-
@Test
36-
fun `crc16 size is two bytes`() {
37-
assertEquals(2, crc16.size)
38-
}
12+
@Test fun `crc8 size is one byte`() = assertEquals(1, Crc8.size)
3913

40-
@Test
41-
fun `crc16 is deterministic for same input`() {
42-
val data = "some test payload"
43-
val c1 = digest(data)
44-
val c2 = digest(data)
45-
assertContentEquals(c1, c2)
46-
}
14+
@Test fun `crc8 smbus check value 123456789`() =
15+
assertContentEquals(hex("F4"), Crc8.digest("123456789"))
4716

48-
@Test
49-
fun `crc16 ccitt-false check value 123456789`() {
50-
// CRC-16/CCITT-FALSE: poly=0x1021, init=0xFFFF, refin=false, refout=false, xorOut=0x0000
51-
val ccittFalse = Crc16(
52-
poly = 0x1021,
53-
init = 0xFFFF,
54-
refin = false,
55-
refout = false,
56-
xorOut = 0x0000,
57-
width = 16
58-
)
59-
60-
val crc = ccittFalse.digest("123456789".toByteArray())
61-
val expected = 0x29B1.toBytesBigEndian(2)
62-
assertContentEquals(expected, crc)
63-
}
64-
}
17+
@Test fun `crc8 smbus empty input`() =
18+
assertContentEquals(hex("00"), Crc8.digest(""))
6519

66-
class Crc32Test {
20+
@Test fun `crc8 rohc check value 123456789`() {
21+
val rohc = Crc8(poly = 0x07, init = 0xFF, refin = true, refout = true, xorOut = 0x00)
22+
assertContentEquals(hex("D0"), rohc.digest("123456789"))
23+
}
6724

68-
private val crc32 = Crc32() // default = CRC-32/ISO-HDLC
25+
@Test fun `crc16 size is two bytes`() = assertEquals(2, Crc16.size)
6926

70-
private fun digest(s: String): ByteArray =
71-
crc32.digest(s.toByteArray())
27+
@Test fun `crc16 x25 check value 123456789`() =
28+
assertContentEquals(hex("906E"), Crc16.digest("123456789"))
7229

73-
private fun Int.toBytesBigEndian(size: Int): ByteArray {
74-
val out = ByteArray(size)
75-
for (i in 0 until size) {
76-
val shift = 8 * (size - 1 - i)
77-
out[i] = ((this ushr shift) and 0xFF).toByte()
78-
}
79-
return out
80-
}
30+
@Test fun `crc16 x25 empty input`() =
31+
assertContentEquals(hex("0000"), Crc16.digest(""))
8132

82-
@Test
83-
fun `crc32 check value 123456789`() {
84-
val crc = digest("123456789")
85-
val expected = 0xCBF43926.toInt().toBytesBigEndian(4)
86-
assertContentEquals(expected, crc)
33+
@Test fun `crc16 ccitt-false check value 123456789`() {
34+
val ccittFalse = Crc16(poly = 0x1021, init = 0xFFFF, refin = false, refout = false, xorOut = 0x0000)
35+
assertContentEquals(hex("29B1"), ccittFalse.digest("123456789"))
8736
}
8837

89-
@Test
90-
fun `crc32 empty string`() {
91-
// For standard CRC-32 (init=FFFFFFFF, xorOut=FFFFFFFF, refin+refout), empty message -> 0x00000000
92-
val crc = digest("")
93-
val expected = 0x00000000.toBytesBigEndian(4)
94-
assertContentEquals(expected, crc)
95-
}
38+
@Test fun `crc32 size is four bytes`() = assertEquals(4, Crc32.size)
9639

97-
@Test
98-
fun `crc32 size is four bytes`() {
99-
assertEquals(4, crc32.size)
100-
}
40+
@Test fun `crc32 iso-hdlc check value 123456789`() =
41+
assertContentEquals(hex("CBF43926"), Crc32.digest("123456789"))
10142

102-
@Test
103-
fun `crc32 is deterministic for same input`() {
104-
val data = "some test payload"
105-
val c1 = digest(data)
106-
val c2 = digest(data)
107-
assertContentEquals(c1, c2)
108-
}
43+
@Test fun `crc32 iso-hdlc empty input`() =
44+
assertContentEquals(hex("00000000"), Crc32.digest(""))
10945

110-
@Test
111-
fun `crc32 mpeg-2 check value 123456789`() {
112-
// CRC-32/MPEG-2: poly=0x04C11DB7, init=0xFFFFFFFF, refin=false, refout=false, xorOut=0x00000000
113-
val mpeg2 = Crc32(
114-
poly = 0x04C11DB7,
115-
init = 0xFFFFFFFF.toInt(),
116-
refin = false,
117-
refout = false,
118-
xorOut = 0x00000000,
119-
width = 32
120-
)
121-
122-
val crc = mpeg2.digest("123456789".toByteArray())
123-
val expected = 0x0376E6E7.toInt().toBytesBigEndian(4)
124-
assertContentEquals(expected, crc)
46+
@Test fun `crc32 mpeg-2 check value 123456789`() {
47+
val mpeg2 = Crc32(poly = 0x04C11DB7, init = 0xFFFFFFFF.toInt(), refin = false, refout = false, xorOut = 0x00000000)
48+
assertContentEquals(hex("0376E6E7"), mpeg2.digest("123456789"))
12549
}
12650
}

0 commit comments

Comments
 (0)