@@ -7,14 +7,15 @@ import kotlinx.serialization.modules.SerializersModule
77 * Holds the configuration for an [EncodedFormat] instance.
88 *
99 * @property codec The ASCII-safe byte codec used to turn raw bytes into text (e.g., Base62, Base64).
10- * @property checksum An optional checksum appended to the binary payload and verified upon decoding.
10+ * @property transform An optional [PayloadTransform] applied after serialization and before encoding.
11+ * Common uses: integrity checks via [Checksum.asTransform], encryption, or error-correcting codes.
1112 * @property binaryFormat The underlying binary serialization format used before encoding to text.
1213 * @property compactZeros When true, leading zero bytes are stripped before encoding and restored on decode.
1314 * A varint prefix encodes the count, costing 1 byte for up to 127 stripped bytes.
1415 */
1516data class EncodedConfiguration (
1617 val codec : ByteEncoding = Base62 ,
17- val checksum : Checksum ? = null ,
18+ val transform : PayloadTransform ? = null ,
1819 val binaryFormat : BinaryFormat = PackedFormat .Default ,
1920 val compactZeros : Boolean = false ,
2021)
@@ -23,16 +24,16 @@ data class EncodedConfiguration(
2324 * Text `StringFormat` that produces short, predictable string tokens by composing:
2425 *
2526 * 1. A binary format (e.g. [PackedFormat], `ProtoBuf`).
26- * 2. An optional checksum.
27+ * 2. An optional [PayloadTransform] ( checksum, encryption, ECC, …) .
2728 * 3. An ASCII-safe byte encoding (e.g. [Base62], [Base64], [Base36], [Base85]).
2829 *
2930 * Typical use:
30- * - `encodeToString`: serialize -> (optionally) append checksum -> encode bytes to text.
31- * - `decodeFromString`: decode text to bytes -> (optionally) verify checksum -> deserialize.
31+ * - `encodeToString`: serialize -> transform.encode -> (optionally) compact zeros -> encode bytes to text.
32+ * - `decodeFromString`: decode text to bytes -> (optionally) restore zeros -> transform.decode -> deserialize.
3233 *
3334 * Use the [EncodedFormat] builder function to create a customized instance.
3435 *
35- * @property configuration The active configuration dictating the codec, checksum , and binary format.
36+ * @property configuration The active configuration dictating the codec, transform , and binary format.
3637 */
3738@OptIn(ExperimentalSerializationApi ::class )
3839open class EncodedFormat (
@@ -44,72 +45,45 @@ open class EncodedFormat(
4445 */
4546 constructor (
4647 codec: ByteEncoding = Base62 ,
47- checksum : Checksum ? = null ,
48+ transform : PayloadTransform ? = null ,
4849 binaryFormat: BinaryFormat = PackedFormat ,
4950 compactZeros: Boolean = true ,
50- ) : this (EncodedConfiguration (codec, checksum , binaryFormat, compactZeros))
51+ ) : this (EncodedConfiguration (codec, transform , binaryFormat, compactZeros))
5152
5253 /* *
5354 * Delegates to the underlying [BinaryFormat]'s serializers module.
5455 */
5556 override val serializersModule: SerializersModule get() = configuration.binaryFormat.serializersModule
5657
5758 /* *
58- * Default format: `PackedFormat` + `Base62` without a checksum .
59+ * Default format: `PackedFormat` + `Base62` without a transform .
5960 */
6061 companion object Default : EncodedFormat()
6162
6263 /* *
63- * Serializes [value] with the configured binary format, optionally appends a checksum ,
64+ * Serializes [value] with the configured binary format, applies the transform ,
6465 * and encodes the resulting byte array using the text codec.
6566 */
66- override fun <T > encodeToString (
67- serializer : SerializationStrategy <T >, value : T
68- ): String {
69- val bytes =
70- configuration.binaryFormat.encodeToByteArray(serializer, value)
71- val checked = if (configuration.checksum != null ) {
72- bytes + configuration.checksum.digest(bytes)
73- } else bytes
74- val payload = if (configuration.compactZeros) compactZerosEncode(checked) else checked
67+ override fun <T > encodeToString (serializer : SerializationStrategy <T >, value : T ): String {
68+ val bytes = configuration.binaryFormat.encodeToByteArray(serializer, value)
69+ val transformed = configuration.transform?.encode(bytes) ? : bytes
70+ val payload = if (configuration.compactZeros) compactZerosEncode(transformed) else transformed
7571 return configuration.codec.encode(payload)
7672 }
7773
7874 /* *
79- * Decodes [string] using the text codec, optionally verifies and strips the checksum ,
80- * then deserializes the remaining bytes with the configured binary format.
75+ * Decodes [string] using the text codec, applies the inverse transform ,
76+ * then deserializes with the configured binary format.
8177 *
82- * @throws IllegalArgumentException if a checksum is configured and the verification fails .
78+ * @throws IllegalArgumentException if the transform's decode step fails (e.g. checksum mismatch) .
8379 */
84- override fun <T > decodeFromString (
85- deserializer : DeserializationStrategy <T >, string : String
86- ): T {
80+ override fun <T > decodeFromString (deserializer : DeserializationStrategy <T >, string : String ): T {
8781 val input = configuration.codec.decode(string)
8882 val raw = if (configuration.compactZeros) compactZerosDecode(input) else input
89- val bytes = if (configuration.checksum != null ) {
90- require(raw.size >= configuration.checksum.size) {
91- " Input too short to contain checksum: expected at least ${configuration.checksum.size} bytes but got ${raw.size} ."
92- }
93- val bytes =
94- raw.sliceArray(0 .. < raw.size - configuration.checksum.size)
95- val actual =
96- raw.sliceArray(raw.size - configuration.checksum.size.. < raw.size)
97- val expected = configuration.checksum.digest(bytes)
98- require(actual.contentEquals(expected)) {
99- " Checksum mismatch."
100- }
101- bytes
102- } else raw
103- return configuration.binaryFormat.decodeFromByteArray(
104- deserializer,
105- bytes
106- )
83+ val bytes = configuration.transform?.decode(raw) ? : raw
84+ return configuration.binaryFormat.decodeFromByteArray(deserializer, bytes)
10785 }
10886
109- /* *
110- * Strips leading zero bytes from [bytes], prepends a varint encoding of the
111- * count, and returns the result.
112- */
11387 private fun compactZerosEncode (bytes : ByteArray ): ByteArray {
11488 var k = 0
11589 while (k < bytes.size && bytes[k] == 0 .toByte()) k++
@@ -120,10 +94,6 @@ open class EncodedFormat(
12094 return result
12195 }
12296
123- /* *
124- * Reads a varint prefix written by [compactZerosEncode] and restores the leading
125- * zero bytes, returning the original byte array.
126- */
12797 private fun compactZerosDecode (bytes : ByteArray ): ByteArray {
12898 require(bytes.isNotEmpty()) { " Compact payload cannot be empty." }
12999 val (k, prefixLen) = varintDecode(bytes)
@@ -146,35 +116,23 @@ open class EncodedFormat(
146116 * Builder for configuring [EncodedFormat] instances.
147117 */
148118class EncodedFormatBuilder {
149- /* *
150- * The ASCII-safe byte codec used to turn raw bytes into text. Defaults to [Base62].
151- */
152119 var codec: ByteEncoding = Base62
153-
154- /* *
155- * An optional checksum appended to the binary payload. Defaults to `null`.
156- */
157- var checksum: Checksum ? = null
158-
159- /* *
160- * The underlying binary serialization format. Defaults to [PackedFormat.Default].
161- */
120+ var transform: PayloadTransform ? = null
162121 var binaryFormat: BinaryFormat = PackedFormat .Default
163-
164- /* *
165- * When true, leading zero bytes are stripped before encoding and restored on decode.
166- * Defaults to `true`.
167- */
168122 var compactZeros: Boolean = true
123+
124+ var checksum: Checksum ?
125+ get() = null
126+ set(value) { transform = value?.asTransform() }
169127}
170128
171129/* *
172130 * Creates a customized [EncodedFormat] instance.
173131 *
174132 * ```
175133 * val format = EncodedFormat {
176- * codec = Base36
177- * checksum = Crc16
134+ * codec = Base36
135+ * transform = Crc16.asTransform()
178136 * }
179137 * ```
180138 *
@@ -187,18 +145,15 @@ fun EncodedFormat(
187145): EncodedFormat {
188146 val builder = EncodedFormatBuilder ().apply {
189147 codec = from.configuration.codec
190- checksum = from.configuration.checksum
148+ transform = from.configuration.transform
191149 binaryFormat = from.configuration.binaryFormat
192150 compactZeros = from.configuration.compactZeros
193151 }
194152 builder.builderAction()
195-
196- val newConfig = EncodedConfiguration (
153+ return EncodedFormat (EncodedConfiguration (
197154 builder.codec,
198- builder.checksum ,
155+ builder.transform ,
199156 builder.binaryFormat,
200157 builder.compactZeros,
201- )
202-
203- return EncodedFormat (newConfig)
158+ ))
204159}
0 commit comments