Skip to content

Commit 4685b7c

Browse files
Implemented check sum field checks (#33)
Co-authored-by: Oleg Smirnov <smirnov_oleg96@mail.ru>
1 parent bc0351b commit 4685b7c

File tree

7 files changed

+269
-32
lines changed

7 files changed

+269
-32
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ Component benchmark results available [here](docs/benchmarks/jmh-benchmark.md).
5353
+ codec checks:
5454
+ that tag value may not contain leading zeros.
5555
+ BodyLength field.
56+
* CheckSum filed.
5657

5758
### 0.1.3
5859
+ Updated:

src/main/kotlin/com/exactpro/th2/codec/fixng/ByteBufUtil.kt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ const val SEP_BYTE = '='.code.toByte()
2525
const val DIGIT_0 = '0'.code.toByte()
2626
const val DIGIT_9 = '9'.code.toByte()
2727

28+
const val SOH_CHAR = ''
29+
const val SOH_BYTE = SOH_CHAR.code.toByte()
30+
31+
@Suppress("KotlinConstantConditions")
2832
private fun Int.getDigitCount(): Int = when {
2933
this < 10 -> 1
3034
this < 100 -> 2
@@ -140,11 +144,19 @@ fun ByteBuf.writeField(tag: Int, value: Any?, delimiter: Byte, charset: Charset)
140144
writeField(tag, value.toString(), delimiter, charset)
141145

142146
fun ByteBuf.writeChecksum(delimiter: Byte) {
147+
val checksum = calculateChecksum(delimiter)
148+
writeTag(10).printInt(checksum, 3).writeByte(delimiter.toInt())
149+
}
150+
151+
fun ByteBuf.calculateChecksum(delimiter: Byte): Int {
143152
val index = readerIndex()
144153
var checksum = 0
145-
while (isReadable) checksum += readByte()
154+
while (isReadable) {
155+
val value = readByte()
156+
checksum += if (value == delimiter) SOH_BYTE else value
157+
}
146158
readerIndex(index)
147-
writeTag(10).printInt(checksum % 256, 3).writeByte(delimiter.toInt())
159+
return checksum % 256
148160
}
149161

150162
fun ByteBuf.getLastTagIndex(delimiter: Byte): Int {

src/main/kotlin/com/exactpro/th2/codec/fixng/FixNgCodec.kt

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ class FixNgCodec(dictionary: IDictionaryStructure, settings: FixNgCodecSettings)
162162
error(errorMessage)
163163
}
164164

165+
validateCheckSum(isDirty, context, buffer)
166+
165167
header["BeginString"] = beginString
166168
header["BodyLength"] = if (isDecodeToStrings) bodyLengthString else bodyLength
167169
header["MsgType"] = msgType
@@ -182,6 +184,66 @@ class FixNgCodec(dictionary: IDictionaryStructure, settings: FixNgCodecSettings)
182184
return MessageGroup(messages)
183185
}
184186

187+
/**
188+
* Information about 'CheckSum' field from FIX specification:
189+
* Three byte, simple checksum (see Volume 2: "Checksum Calculation" for description).
190+
* ALWAYS LAST FIELD IN MESSAGE; i.e. serves, with the trailing <SOH>, as the end-of-message delimiter.
191+
* Always defined as three characters. (Always unencrypted)
192+
*/
193+
private fun validateCheckSum(
194+
isDirty: Boolean,
195+
context: IReportingContext,
196+
buffer: ByteBuf,
197+
) {
198+
val index = buffer.readerIndex()
199+
try {
200+
val lastTagIndex = buffer.indexOf(buffer.writerIndex() - 1, 0, decodeDelimiter) + 1
201+
buffer.readerIndex(lastTagIndex)
202+
val lastTag = buffer.readTag { tag, warn -> { LOGGER.warn { "Tag ($tag) reading problem: $warn" } } }
203+
if (lastTag != TAG_CHECKSUM) {
204+
handleError(
205+
isDirty, context,
206+
"Message ends with $lastTag tag instead of CheckSum ($TAG_CHECKSUM)",
207+
)
208+
return
209+
}
210+
val value = buffer.readValue(decodeDelimiter, charset, isDirty)
211+
val valueSize = value.toByteArray(charset).size
212+
if (valueSize != 3) {
213+
handleError(
214+
isDirty, context,
215+
"CheckSum ($TAG_CHECKSUM) field must have 3 bytes length, instead of size: $valueSize, value: '$value'",
216+
)
217+
return
218+
}
219+
val checksum = value.toIntOrNull()
220+
if (checksum == null) {
221+
handleError(
222+
isDirty, context,
223+
"CheckSum ($TAG_CHECKSUM) field value must consist of digits only instead of '$value'",
224+
)
225+
return
226+
}
227+
if (checksum !in 0..255) {
228+
handleError(
229+
isDirty, context,
230+
"CheckSum ($TAG_CHECKSUM) field must have value from 0 to 255 included both limits instead of '$checksum' value"
231+
)
232+
return
233+
}
234+
val calculatedChecksum = buffer.slice(0, lastTagIndex)
235+
.calculateChecksum(decodeDelimiter)
236+
if (checksum != calculatedChecksum) {
237+
handleError(
238+
isDirty, context,
239+
"CheckSum ($TAG_CHECKSUM) field has $checksum value which isn't matched to calculated value $calculatedChecksum"
240+
)
241+
}
242+
} finally {
243+
buffer.readerIndex(index)
244+
}
245+
}
246+
185247
/**
186248
* Information about 'BodyLength' field from FIX specification:
187249
* Message length, in bytes, forward to the CheckSum field.
@@ -603,8 +665,6 @@ class FixNgCodec(dictionary: IDictionaryStructure, settings: FixNgCodecSettings)
603665

604666
companion object {
605667
private val LOGGER = KotlinLogging.logger { }
606-
const val SOH_CHAR = ''
607-
private const val SOH_BYTE = SOH_CHAR.code.toByte()
608668

609669
private const val HEADER = "header"
610670
private const val TRAILER = "trailer"

src/main/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecSettings.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 Exactpro (Exactpro Systems Limited)
2+
* Copyright 2023-2025 Exactpro (Exactpro Systems Limited)
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
1717
package com.exactpro.th2.codec.fixng
1818

1919
import com.exactpro.th2.codec.api.IPipelineCodecSettings
20-
import com.exactpro.th2.codec.fixng.FixNgCodec.Companion.SOH_CHAR
2120
import com.fasterxml.jackson.core.JsonParser
2221
import com.fasterxml.jackson.databind.DeserializationContext
2322
import com.fasterxml.jackson.databind.JsonDeserializer
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2023-2025 Exactpro (Exactpro Systems Limited)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.exactpro.th2.codec.fixng
18+
19+
import io.netty.buffer.Unpooled
20+
import org.junit.jupiter.api.Assertions.assertEquals
21+
import org.junit.jupiter.params.ParameterizedTest
22+
import org.junit.jupiter.params.provider.ValueSource
23+
import kotlin.charArrayOf
24+
25+
26+
class ByteBufUtilKtTest {
27+
28+
@ParameterizedTest
29+
@ValueSource(chars = ['', '|'])
30+
fun `calculate checksum test`(delimiter: Char) {
31+
val actual = Unpooled.wrappedBuffer(MSG_CORRECT.dropLast(7).replaceSoh(delimiter).toByteArray(Charsets.US_ASCII)).calculateChecksum(
32+
delimiter.code.toByte()
33+
)
34+
assertEquals(191, actual)
35+
}
36+
37+
companion object {
38+
private const val MSG_CORRECT = "8=FIXT.1.19=29535=849=SENDER56=RECEIVER34=1094752=20230419-10:36:07.41508817=49550466211=zSuNbrBIZyVljs41=zSuNbrBIZyVljs37=49415882150=039=0151=50014=50048=NWDR22=8453=2448=NGALL1FX01447=D452=76448=0447=P452=31=test40=A59=054=B55=ABC38=50044=100047=50060=20180205-10:38:08.00000810=191"
39+
}
40+
}

0 commit comments

Comments
 (0)