diff --git a/.github/workflows/build-dev-release.yml b/.github/workflows/build-dev-release.yml new file mode 100644 index 0000000..b438d11 --- /dev/null +++ b/.github/workflows/build-dev-release.yml @@ -0,0 +1,15 @@ +name: Build and publish dev release Docker image to Github Container Registry ghcr.io + +on: workflow_dispatch + +jobs: + build: + uses: th2-net/.github/.github/workflows/compound-java.yml@main + with: + build-target: 'Docker' + devRelease: true + createTag: true + docker-username: ${{ github.actor }} + secrets: + docker-password: ${{ secrets.GITHUB_TOKEN }} + nvd-api-key: ${{ secrets.NVD_APIKEY }} \ No newline at end of file diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml new file mode 100644 index 0000000..dcf70be --- /dev/null +++ b/.github/workflows/build-release.yml @@ -0,0 +1,15 @@ +name: Build and publish release Docker image to Github Container Registry ghcr.io + +on: workflow_dispatch + +jobs: + build: + uses: th2-net/.github/.github/workflows/compound-java.yml@main + with: + build-target: 'Docker' + devRelease: false + createTag: true + docker-username: ${{ github.actor }} + secrets: + docker-password: ${{ secrets.GITHUB_TOKEN }} + nvd-api-key: ${{ secrets.NVD_APIKEY }} \ No newline at end of file diff --git a/.github/workflows/build-sanpshot.yml b/.github/workflows/build-sanpshot.yml new file mode 100644 index 0000000..9366a44 --- /dev/null +++ b/.github/workflows/build-sanpshot.yml @@ -0,0 +1,20 @@ +name: Build and publish Docker image to Github Container Registry ghcr.io + +on: + push: + branches-ignore: + - master + - version-* + - dependabot** + paths-ignore: + - README.md + +jobs: + build-job: + uses: th2-net/.github/.github/workflows/compound-java-dev.yml@main + with: + build-target: 'Docker' + docker-username: ${{ github.actor }} + secrets: + docker-password: ${{ secrets.GITHUB_TOKEN }} + nvd-api-key: ${{ secrets.NVD_APIKEY }} \ No newline at end of file diff --git a/.github/workflows/ci-unwelcome-words.yml b/.github/workflows/ci-unwelcome-words.yml new file mode 100644 index 0000000..39d4010 --- /dev/null +++ b/.github/workflows/ci-unwelcome-words.yml @@ -0,0 +1,23 @@ +name: CI + +on: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.sha }} + - name: Checkout tool + uses: actions/checkout@v4 + with: + repository: exactpro-th2/ci-github-action + ref: master + token: ${{ secrets.PAT_CI_ACTION }} + path: ci-github-action + - name: Run CI action + uses: ./ci-github-action + with: + ref: ${{ github.sha }} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e7f2eee --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +build/ +shared/ +.gradle/ +.idea/ +logs/ +out/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..432f34f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM gradle:8.7-jdk11 AS build +ARG release_version +COPY ./ . +RUN gradle --no-daemon clean build dockerPrepare -Prelease_version=${release_version} + +FROM adoptopenjdk/openjdk11:alpine +WORKDIR /home +COPY --from=build /home/gradle/build/docker . +ENTRYPOINT ["/home/service/bin/service"] \ No newline at end of file diff --git a/README.md b/README.md index bde2b31..33815b0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,57 @@ -# th2-codec-fix-ng -Yet another FIX codec +# th2-codec-fix-ng 0.1.0 + +This codec can be used in dirty mode for decoding and encoding messages via the FIX protocol. + +## Configuration + +### Codec factory + +To use the FIX codec you will need to specify the following codec factory: +**com.exactpro.th2.codec.fixng.FixNgCodecFactory** + +### Configuration parameters +Configuration example. +```yaml +beginString: FIXT.1.1 +dictionary: fix_dictionary.xml +charset: US_ASCII +dirtyMode: false +decodeValuesToStrings: true +decodeComponentsToNestedMaps: true +``` + +#### beginString +default value: `FIXT.1.1`. Value to put into the `BeginString` field (tag: 8) when encoding messages. + +#### dictionary +required value. XML file containing the FIX dictionary. + +#### charset +default value: `US_ASCII`. Charset for reading and writing FIX fields. + +#### dirtyMode +default value: `false`. If `true`, processes all messages in dirty mode (generates warnings on invalid messages and continues processing). If `false`, only messages that contain the `encode-mode: dirty` property will be processed in dirty mode. + +#### decodeValuesToStrings +default value: `true`. If `true`, decodes all values to strings instead of typed values. + +#### decodeComponentsToNestedMaps +default value: `true`. If `true`, decodes `components` to nested maps instead of unwrap component's map to message's main map. + +## Performance +Component benchmark results available [here](docs/benchmarks/jmh-benchmark.md). + +## Release notes +### 0.1.0 + + Dirty mode added. + + `dirtyMode` setting option added. + + `decodeValuesToStrings` setting option added. + + JMH benchmarks added + + Migrate to th2 gradle plugin `0.1.2` (th2-bom:4.7.0) + + Updated th2-common: `5.11.0-dev` + + Updated th2-codec: `5.5.0-dev` + + Updated sailfish: `3.3.241` + + Workflows updated + +### 0.0.1 + + Initial release \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..57b3558 --- /dev/null +++ b/build.gradle @@ -0,0 +1,105 @@ +plugins { + id "application" + id "com.exactpro.th2.gradle.component" version "0.1.2" + id "org.jetbrains.kotlin.jvm" version "$kotlin_version" + id "org.jetbrains.kotlin.kapt" version "$kotlin_version" + id "me.champeau.jmh" version "0.7.2" +} + +ext { + sailfishVersion = '3.3.241' + commonVersion = '5.14.0-dev' + jmhVersion = '1.37' +} + +group = 'com.exactpro.th2' +version = release_version + +ext.excludeSailfish = { rcd -> + rcd.excludeModule("com.exactpro.sf", "sailfish-core") + rcd.excludeModule("com.exactpro.sf", "sailfish-common") + rcd.excludeModule("com.exactpro.sf", "sailfish-rest-api-client") + rcd.excludeModule("com.exactpro.sf", "service-http") +} + +repositories { + maven { + name 'Sonatype_snapshots' + url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' + content { + excludeSailfish(it) + } + } + + // ignoreGradleMetadataRedirection is used for sonatype because + // Sailfish dependencies have constrains that interfere with our BOM + // so we exclude Gradle metadata for this repositories. + // We've checked these versions - they are compatible and safe to use + maven { + name 'Sonatype_snapshots' + url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' + metadataSources { + mavenPom() + artifact() + ignoreGradleMetadataRedirection() + } + } + + maven { + name 'Sonatype_releases' + url 'https://s01.oss.sonatype.org/content/repositories/releases/' + content { + excludeSailfish(it) + } + } + + maven { + name 'Sonatype_releases' + url 'https://s01.oss.sonatype.org/content/repositories/releases/' + metadataSources { + mavenPom() + artifact() + ignoreGradleMetadataRedirection() + } + } + + mavenCentral() + mavenLocal() + + configurations.configureEach { + resolutionStrategy.cacheChangingModulesFor 0, 'seconds' + resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds' + } +} + +dependencies { + implementation "com.exactpro.th2:common:$commonVersion" + implementation "com.exactpro.th2:codec:5.5.0-dev" + + implementation ("com.exactpro.sf:sailfish-common:$sailfishVersion") { + exclude group: "commons-configuration", module: "commons-configuration" // because of the vulnerability + exclude group: "com.fasterxml.jackson.dataformat", module: "jackson-dataformat-yaml" // because of the vulnerability + exclude group: "com.exactpro.mina", module: "apache-mina-core" // because of the vulnerability + exclude group: "javax.activation", module: "javax.activation-api" // because of the license + } + + implementation "io.netty:netty-buffer" + implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + implementation "io.github.oshai:kotlin-logging:5.1.4" + + compileOnly "com.google.auto.service:auto-service:1.1.1" + annotationProcessor "com.google.auto.service:auto-service:1.1.1" + kapt "com.google.auto.service:auto-service:1.1.1" + + jmh "org.openjdk.jmh:jmh-core:$jmhVersion" + jmh "org.openjdk.jmh:jmh-generator-annprocess:$jmhVersion" + jmh "org.openjdk.jmh:jmh-generator-bytecode:$jmhVersion" + + testImplementation "org.junit.jupiter:junit-jupiter:5.11.0" + testImplementation "org.jetbrains.kotlin:kotlin-test-junit5" + testImplementation "org.assertj:assertj-core:3.26.3" +} + +test.useJUnitPlatform() +application.mainClass = "com.exactpro.th2.codec.MainKt" +dependencyCheck.suppressionFile = "suppressions.xml" \ No newline at end of file diff --git a/docs/benchmarks/jmh-benchmark.md b/docs/benchmarks/jmh-benchmark.md new file mode 100644 index 0000000..23d435e --- /dev/null +++ b/docs/benchmarks/jmh-benchmark.md @@ -0,0 +1,84 @@ +# JMH benchmark + +encode the following FIX message: + +```kotlin + ParsedMessage( + MessageId("test_alias", Direction.OUTGOING, 0L, Instant.now(), emptyList()), + EventId("test_id", "test_book", "test_scope", Instant.now()), + "ExecutionReport", + mutableMapOf("encode-mode" to "dirty"), + PROTOCOL, + mutableMapOf( + "header" to mutableMapOf( + "MsgSeqNum" to 10947, + "SenderCompID" to "SENDER", + "SendingTime" to LocalDateTime.parse("2023-04-19T10:36:07.415088"), + "TargetCompID" to "RECEIVER", + "BeginString" to "FIXT.1.1", + "BodyLength" to 295, + "MsgType" to "8" + ), + "ExecID" to "495504662", + "ClOrdID" to "zSuNbrBIZyVljs", + "OrigClOrdID" to "zSuNbrBIZyVljs", + "OrderID" to "49415882", + "ExecType" to '0', + "OrdStatus" to '0', + "LeavesQty" to BigDecimal(500), + "CumQty" to BigDecimal(500), + "SecurityID" to "NWDR", + "SecurityIDSource" to "8", + "TradingParty" to mutableMapOf( + "NoPartyIDs" to mutableListOf( + mutableMapOf( + "PartyID" to "NGALL1FX01", + "PartyIDSource" to 'D', + "PartyRole" to 76 + ), + mutableMapOf( + "PartyID" to "0", + "PartyIDSource" to 'P', + "PartyRole" to 3 + ) + ) + ), + "Account" to "test", + "OrdType" to 'A', + "TimeInForce" to '0', + "Side" to 'B', + "Symbol" to "ABC", + "OrderQty" to BigDecimal(500), + "Price" to BigDecimal(1000), + "Unknown" to "500", + "TransactTime" to LocalDateTime.parse("2018-02-05T10:38:08.000008"), + "trailer" to mutableMapOf( + "CheckSum" to "191" + ) + ) + ) +``` + +decode the same FIX message: + +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 + +Testing is carried out in two formats of parsed messages: String values and Typed values. + +## benchmark results for version 0.1.0-dev + +dirty mode: + +Benchmark Mode Cnt Score Error Units +FixNgCodecBenchmark.encodeFixMessageString thrpt 25 178479.225 ± 2851.079 ops/s +FixNgCodecBenchmark.encodeFixMessageTyped thrpt 25 263077.629 ± 3967.905 ops/s +FixNgCodecBenchmark.parseFixMessageString thrpt 25 173370.305 ± 1878.013 ops/s +FixNgCodecBenchmark.parseFixMessageTyped thrpt 25 186232.291 ± 1295.186 ops/s + +strict mode: + +Benchmark Mode Cnt Score Error Units +FixNgCodecBenchmark.encodeFixMessageString thrpt 25 179523.040 ± 3084.493 ops/s +FixNgCodecBenchmark.encodeFixMessageTyped thrpt 25 265769.868 ± 3893.223 ops/s +FixNgCodecBenchmark.parseFixMessageString thrpt 25 165978.593 ± 6474.860 ops/s +FixNgCodecBenchmark.parseFixMessageTyped thrpt 25 186475.155 ± 1548.224 ops/s \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..541b1e1 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,5 @@ +kotlin.code.style=official +kotlin_version=1.8.22 +release_version=0.1.0 + +vcs_url=https://github.com/th2-net/th2-codec-fix-ng \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f3d88b1 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..093df1d --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME \ No newline at end of file diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..2fe81a7 --- /dev/null +++ b/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..9618d8d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..a989c94 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'codec-fix-ng' \ No newline at end of file diff --git a/src/jmh/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecBenchmark.kt b/src/jmh/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecBenchmark.kt new file mode 100644 index 0000000..9cfb560 --- /dev/null +++ b/src/jmh/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecBenchmark.kt @@ -0,0 +1,178 @@ +/* + * Copyright 2024 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.exactpro.th2.codec.fixng + +import com.exactpro.sf.common.messages.structures.IDictionaryStructure +import com.exactpro.sf.common.messages.structures.loaders.XmlDictionaryStructureLoader +import com.exactpro.th2.codec.api.IPipelineCodec +import com.exactpro.th2.codec.api.IReportingContext +import com.exactpro.th2.codec.fixng.FixNgCodecFactory.Companion.PROTOCOL +import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.Direction +import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.EventId +import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.MessageGroup +import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.MessageId +import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.ParsedMessage +import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.RawMessage +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled +import org.openjdk.jmh.annotations.Benchmark +import org.openjdk.jmh.annotations.BenchmarkMode +import org.openjdk.jmh.annotations.Level +import org.openjdk.jmh.annotations.Mode +import org.openjdk.jmh.annotations.Scope +import org.openjdk.jmh.annotations.Setup +import org.openjdk.jmh.annotations.State +import java.math.BigDecimal +import java.time.Instant +import java.time.LocalDateTime + +@State(Scope.Benchmark) +open class BenchmarkState { + lateinit var codecTyped: IPipelineCodec + lateinit var codecString: IPipelineCodec + private lateinit var rawBody: ByteBuf + lateinit var rawGroup: MessageGroup + lateinit var parsedGroupTyped: MessageGroup + lateinit var parsedGroupString: MessageGroup + + @Setup(Level.Trial) + fun setup() { + val dictionary: IDictionaryStructure = FixNgCodecTest::class.java.classLoader + .getResourceAsStream("dictionary-benchmark.xml") + .use(XmlDictionaryStructureLoader()::load) + + codecTyped = FixNgCodec(dictionary, FixNgCodecSettings(dictionary = "", decodeValuesToStrings = false)) + codecString = FixNgCodec(dictionary, FixNgCodecSettings(dictionary = "", decodeValuesToStrings = true)) + rawBody = Unpooled.wrappedBuffer(MSG_CORRECT.toByteArray(Charsets.US_ASCII)) + rawGroup = MessageGroup(listOf(RawMessage(id = parsedMessageTyped.id, eventId = parsedMessageTyped.eventId, body = rawBody, metadata = mutableMapOf("encode-mode" to "dirty")))) + parsedGroupTyped = MessageGroup(listOf(parsedMessageTyped)) + parsedGroupString = MessageGroup(listOf(parsedMessageString)) + } + + @Setup(Level.Invocation) + fun resetReader() { + rawBody.resetReaderIndex() + } + + companion object { + private const val MSG_CORRECT = "8=FIXT.1.1\u00019=295\u000135=8\u000149=SENDER\u000156=RECEIVER\u000134=10947\u000152=20230419-10:36:07.415088\u000117=495504662\u000111=zSuNbrBIZyVljs\u000141=zSuNbrBIZyVljs\u000137=49415882\u0001150=0\u000139=0\u0001151=500\u000114=500\u000148=NWDR\u000122=8\u0001453=2\u0001448=NGALL1FX01\u0001447=D\u0001452=76\u0001448=0\u0001447=P\u0001452=3\u00011=test\u000140=A\u000159=0\u000154=B\u000155=ABC\u000138=500\u000144=1000\u000147=500\u000160=20180205-10:38:08.000008\u000110=191\u0001" + + private val parsedMessageTyped = ParsedMessage( + MessageId("test_alias", Direction.OUTGOING, 0L, Instant.now(), emptyList()), + EventId("test_id", "test_book", "test_scope", Instant.now()), + "ExecutionReport", + mutableMapOf("encode-mode" to "dirty"), + PROTOCOL, + mutableMapOf( + "header" to mutableMapOf( + "MsgSeqNum" to 10947, + "SenderCompID" to "SENDER", + "SendingTime" to LocalDateTime.parse("2023-04-19T10:36:07.415088"), + "TargetCompID" to "RECEIVER", + "BeginString" to "FIXT.1.1", + "BodyLength" to 295, + "MsgType" to "8" + ), + "ExecID" to "495504662", + "ClOrdID" to "zSuNbrBIZyVljs", + "OrigClOrdID" to "zSuNbrBIZyVljs", + "OrderID" to "49415882", + "ExecType" to '0', + "OrdStatus" to '0', + "LeavesQty" to BigDecimal(500), + "CumQty" to BigDecimal(500), + "SecurityID" to "NWDR", + "SecurityIDSource" to "8", + "TradingParty" to mutableMapOf( + "NoPartyIDs" to mutableListOf( + mutableMapOf( + "PartyID" to "NGALL1FX01", + "PartyIDSource" to 'D', + "PartyRole" to 76 + ), + mutableMapOf( + "PartyID" to "0", + "PartyIDSource" to 'P', + "PartyRole" to 3 + ) + ) + ), + "Account" to "test", + "OrdType" to 'A', + "TimeInForce" to '0', + "Side" to 'B', + "Symbol" to "ABC", + "OrderQty" to BigDecimal(500), + "Price" to BigDecimal(1000), + "Unknown" to "500", + "TransactTime" to LocalDateTime.parse("2018-02-05T10:38:08.000008"), + "trailer" to mutableMapOf( + "CheckSum" to "191" + ) + ) + ) + + @Suppress("UNCHECKED_CAST") + private val parsedMessageString = ParsedMessage( + MessageId("test_alias", Direction.OUTGOING, 0L, Instant.now(), emptyList()), + EventId("test_id", "test_book", "test_scope", Instant.now()), + "ExecutionReport", + mutableMapOf(/*"encode-mode" to "dirty"*/), + PROTOCOL, + convertValuesToString(parsedMessageTyped.body) as MutableMap + ) + + private fun convertValuesToString(value: Any?): Any = when (value) { + is Map<*, *> -> value.mapValues { convertValuesToString(it.value) } + is List<*> -> value.map(::convertValuesToString) + else -> value.toString() + } + } +} + +open class FixNgCodecBenchmark { + @Benchmark + @BenchmarkMode(Mode.Throughput) + fun encodeFixMessageTyped(state: BenchmarkState) { + state.codecTyped.encode(state.parsedGroupTyped, context) + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + fun parseFixMessageTyped(state: BenchmarkState) { + state.codecTyped.decode(state.rawGroup, context) + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + fun encodeFixMessageString(state: BenchmarkState) { + state.codecString.encode(state.parsedGroupString, context) + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + fun parseFixMessageString(state: BenchmarkState) { + state.codecString.decode(state.rawGroup, context) + } + + companion object { + private val context = object : IReportingContext { + override fun warning(message: String) { } + override fun warnings(messages: Iterable) { } + } + } +} \ No newline at end of file diff --git a/src/jmh/resources/dictionary-benchmark.xml b/src/jmh/resources/dictionary-benchmark.xml new file mode 100644 index 0000000..a04f185 --- /dev/null +++ b/src/jmh/resources/dictionary-benchmark.xml @@ -0,0 +1,7472 @@ + + + + + + + 1409 + INT + 0 + 2 + 3 + 4 + 6 + 7 + 8 + 100 + 101 + 102 + + + 1 + STRING + + + 2 + STRING + + + 3 + STRING + + + 4 + CHAR + B + S + X + T + + + 5 + STRING + N + C + R + + + 6 + PRICE + + + 7 + SEQNUM + + + 8 + STRING + + + 9 + LENGTH + + + 10 + STRING + + + 11 + STRING + + + 12 + AMT + + + 13 + CHAR + 1 + 2 + 3 + 4 + 5 + 6 + + + 14 + QTY + + + 15 + CURRENCY + + + 16 + SEQNUM + + + 17 + STRING + + + 18 + MULTIPLECHARVALUE + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + A + B + C + D + E + F + G + H + I + J + K + L + M + N + O + P + Q + R + S + T + U + V + W + X + Y + Z + a + b + c + d + e + f + g + h + i + j + k + + + 19 + STRING + + + 20 + CHAR + 0 + 1 + 2 + 3 + + + 21 + CHAR + 1 + 2 + 3 + + + 22 + STRING + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + A + B + C + D + E + F + G + H + I + J + K + L + + + 23 + STRING + + + 25 + CHAR + L + M + H + + + 26 + STRING + + + 27 + STRING + 0 + S + M + L + U + + + 28 + CHAR + N + C + R + + + 29 + CHAR + 1 + 2 + 3 + 4 + + + 30 + EXCHANGE + + + 31 + PRICE + + + 32 + QTY + + + 33 + NUMINGROUP + + + 34 + SEQNUM + + + 35 + STRING + 0 + 1 + 2 + 3 + 4 + 5 + 8 + 9 + A + D + E + F + G + S + Z + b + e + f + j + k + q + r + s + u + AD + AE + AF + AI + AQ + AR + BW + BX + + + 36 + SEQNUM + + + 37 + STRING + + + 38 + QTY + + + 39 + CHAR + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + A + B + C + D + E + + + 40 + CHAR + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + A + B + C + D + E + F + G + H + I + J + K + L + M + P + Q + + + 41 + STRING + + + 42 + UTCTIMESTAMP + + + 43 + BOOLEAN + + + 44 + PRICE + + + 45 + SEQNUM + + + 47 + STRING + + + 48 + STRING + + + 49 + STRING + + + 50 + STRING + + + 52 + UTCTIMESTAMP + + + 53 + QTY + + + 54 + CHAR + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + A + B + C + D + E + F + G + + + 55 + STRING + + + 56 + STRING + + + 57 + STRING + + + 58 + STRING + + + 59 + CHAR + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + C + + + 60 + UTCTIMESTAMP + + + 61 + CHAR + 0 + 1 + 2 + + + 62 + UTCTIMESTAMP + + + 63 + STRING + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + C + B + + + 64 + LOCALMKTDATE + + + 65 + STRING + WI + CD + + + 66 + STRING + + + 67 + INT + + + 68 + INT + + + 69 + STRING + + + 70 + STRING + + + 71 + CHAR + 0 + 1 + 2 + 3 + 4 + 5 + 6 + + + 72 + STRING + + + 73 + NUMINGROUP + + + 74 + INT + + + 75 + LOCALMKTDATE + + + 76 + STRING + + + 77 + CHAR + O + C + R + F + + + 78 + NUMINGROUP + + + 79 + STRING + + + 80 + QTY + + + 81 + CHAR + 0 + 1 + 2 + 3 + 4 + 5 + 6 + + + 82 + INT + + + 83 + INT + + + 84 + QTY + + + 85 + NUMINGROUP + + + 86 + STRING + + + 87 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + + + 88 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + + + 89 + DATA + + + 90 + LENGTH + + + 91 + DATA + + + 92 + STRING + + + 93 + LENGTH + + + 94 + CHAR + 0 + 1 + 2 + + + 95 + LENGTH + + + 96 + DATA + + + 97 + BOOLEAN + + + 98 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + + + 99 + PRICE + + + 100 + EXCHANGE + + + 102 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 18 + 99 + 113100 + 113101 + + + 103 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 18 + 99 + 10000 + 10001 + 10003 + 10004 + 10005 + 10006 + + + 104 + CHAR + A + B + C + D + I + L + M + O + P + Q + R + S + T + V + W + X + Y + Z + + + 105 + STRING + + + 106 + STRING + + + 107 + STRING + + + 108 + INT + + + 109 + STRING + + + 110 + QTY + + + 111 + QTY + + + 112 + STRING + + + 113 + BOOLEAN + + + 114 + BOOLEAN + + + 115 + STRING + + + 116 + STRING + + + 117 + STRING + + + 118 + AMT + + + 119 + AMT + + + 120 + CURRENCY + + + 121 + BOOLEAN + + + 122 + UTCTIMESTAMP + + + 123 + BOOLEAN + + + 124 + NUMINGROUP + + + 125 + CHAR + P + F + + + 126 + UTCTIMESTAMP + + + 127 + CHAR + A + B + C + D + E + F + Z + + + 128 + STRING + + + 129 + STRING + + + 130 + BOOLEAN + + + 131 + STRING + + + 132 + PRICE + + + 133 + PRICE + + + 134 + QTY + + + 135 + QTY + + + 136 + NUMINGROUP + + + 137 + AMT + + + 138 + CURRENCY + + + 139 + STRING + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + + 140 + PRICE + + + 141 + BOOLEAN + + + 142 + STRING + + + 143 + STRING + + + 144 + STRING + + + 145 + STRING + + + 146 + NUMINGROUP + + + 147 + STRING + + + 148 + STRING + + + 149 + STRING + + + 150 + CHAR + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + A + B + C + D + E + F + G + H + I + J + K + L + + + 151 + QTY + + + 152 + QTY + + + 153 + PRICE + + + 154 + AMT + + + 155 + FLOAT + + + 156 + CHAR + M + D + + + 157 + INT + + + 158 + PERCENTAGE + + + 159 + AMT + + + 160 + CHAR + 0 + 1 + 2 + 3 + 4 + 5 + + + 161 + STRING + + + 162 + STRING + + + 163 + CHAR + N + C + R + T + + + 164 + STRING + + + 165 + CHAR + 1 + 2 + 3 + + + 166 + STRING + CED + DTC + EUR + FED + ISO + PNY + PTC + + + 167 + STRING + ? + ABS + AMENDED + AN + BA + BN + BOX + BRADY + BRIDGE + BUYSELL + CB + CD + CL + CMBS + CMO + COFO + COFP + CORP + CP + CPP + CS + DEFLTED + DINP + DN + DUAL + EUCD + EUCORP + EUCP + EUSOV + EUSUPRA + FAC + FADN + FOR + FORWARD + FUT + GO + IET + LOFC + LQN + MATURED + MBS + MF + MIO + MLEG + MPO + MPP + MPT + MT + MTN + NONE + ONITE + OPT + PEF + PFAND + PN + PS + PZFJ + RAN + REPLACD + REPO + RETIRED + REV + RVLV + RVLVTRM + SECLOAN + SECPLEDGE + SPCLA + SPCLO + SPCLT + STN + STRUCT + SUPRA + SWING + TAN + TAXA + TBA + TBILL + TBOND + TCAL + TD + TECP + TERM + TINT + TIPS + TNOTE + TPRN + TRAN + UST + USTB + VRDN + WAR + WITHDRN + WLD + XCN + XLINKD + YANK + YCD + OOP + OOF + CASH + + + 168 + UTCTIMESTAMP + + + 169 + INT + 0 + 1 + 2 + 3 + 4 + + + 170 + STRING + + + 171 + STRING + + + 172 + INT + 0 + 1 + 2 + 3 + + + 173 + STRING + + + 174 + STRING + + + 175 + STRING + + + 176 + STRING + + + 177 + STRING + + + 178 + STRING + + + 179 + STRING + + + 180 + STRING + + + 181 + STRING + + + 182 + STRING + + + 183 + STRING + + + 184 + STRING + + + 185 + STRING + + + 186 + STRING + + + 187 + STRING + + + 188 + PRICE + + + 189 + PRICEOFFSET + + + 190 + PRICE + + + 191 + PRICEOFFSET + + + 192 + QTY + + + 193 + LOCALMKTDATE + + + 194 + PRICE + + + 195 + PRICEOFFSET + + + 196 + STRING + + + 197 + INT + 0 + 1 + + + 198 + STRING + + + 199 + NUMINGROUP + + + 200 + MONTH-YEAR + + + 201 + INT + 0 + 1 + + + 202 + PRICE + + + 203 + INT + 0 + 1 + + + 204 + INT + 0 + 1 + + + 205 + DAY-OF-MONTH + + + 206 + CHAR + + + 207 + EXCHANGE + + + 208 + BOOLEAN + + + 209 + INT + 1 + 2 + 3 + + + 210 + QTY + + + 211 + FLOAT + + + 212 + LENGTH + + + 213 + DATA + + + 214 + STRING + + + 215 + NUMINGROUP + + + 216 + INT + 1 + 2 + 3 + 4 + + + 217 + STRING + + + 218 + PRICEOFFSET + + + 219 + CHAR + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + + + 220 + CURRENCY + + + 221 + STRING + EONIA + EUREPO + Euribor + FutureSWAP + LIBID + LIBOR + MuniAAA + OTHER + Pfandbriefe + SONIA + SWAP + Treasury + + + 222 + STRING + + + 223 + PERCENTAGE + + + 224 + LOCALMKTDATE + + + 225 + LOCALMKTDATE + + + 226 + INT + + + 227 + PERCENTAGE + + + 228 + FLOAT + + + 229 + LOCALMKTDATE + + + 230 + LOCALMKTDATE + + + 231 + FLOAT + + + 232 + NUMINGROUP + + + 233 + STRING + ABS + AMT + AUTOREINV + BANKQUAL + BGNCON + COUPON + CPP + CPR + CPY + CURRENCY + CUSTOMDATE + GEOG + HAIRCUT + HEP + INSURED + ISSUE + ISSUER + ISSUESIZE + LOOKBACK + LOT + LOTVAR + MAT + MATURITY + MAXSUBS + MHP + MINDNOM + MININCR + MINQTY + MPR + PAYFREQ + PIECES + PMAX + PPC + PPL + PPM + PPT + PRICE + PRICEFREQ + PROD + PROTECT + PSA + PURPOSE + PXSOURCE + RATING + REDEMPTION + RESTRICTED + SECTOR + SECTYPE + SMM + STRUCT + SUBSFREQ + SUBSLEFT + TEXT + TRDVAR + WAC + WAL + WALA + WAM + WHOLE + YIELD + + + 234 + STRING + CD + XD + CC + XC + CB + XB + CR + XR + CP + XP + CS + SP + TR + GD + + + 235 + STRING + AFTERTAX + ANNUAL + ATISSUE + AVGMATURITY + BOOK + CALL + CHANGE + CLOSE + COMPOUND + CURRENT + GOVTEQUIV + GROSS + INFLATION + INVERSEFLOATER + LASTCLOSE + LASTMONTH + LASTQUARTER + LASTYEAR + LONGAVGLIFE + MARK + MATURITY + NEXTREFUND + OPENAVG + PREVCLOSE + PROCEEDS + PUT + SEMIANNUAL + SHORTAVGLIFE + SIMPLE + TAXEQUIV + TENDER + TRUE + VALUE1_32 + WORST + + + 236 + PERCENTAGE + + + 237 + AMT + + + 238 + AMT + + + 239 + STRING + + + 240 + LOCALMKTDATE + + + 241 + LOCALMKTDATE + + + 242 + LOCALMKTDATE + + + 243 + STRING + + + 244 + INT + + + 245 + PERCENTAGE + + + 246 + FLOAT + + + 247 + LOCALMKTDATE + + + 248 + LOCALMKTDATE + + + 249 + LOCALMKTDATE + + + 250 + STRING + + + 251 + INT + + + 252 + PERCENTAGE + + + 253 + FLOAT + + + 254 + LOCALMKTDATE + + + 255 + STRING + + + 256 + STRING + + + 257 + STRING + + + 258 + BOOLEAN + + + 259 + LOCALMKTDATE + + + 260 + PRICE + + + 262 + STRING + + + 263 + CHAR + 0 + 1 + 2 + + + 264 + INT + + + 265 + INT + 0 + 1 + + + 266 + BOOLEAN + + + 267 + NUMINGROUP + + + 268 + NUMINGROUP + + + 269 + CHAR + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + A + B + C + D + E + F + G + H + J + K + L + M + N + O + P + Q + + + 270 + PRICE + + + 271 + QTY + + + 272 + UTCDATEONLY + + + 273 + UTCTIMEONLY + + + 274 + CHAR + 0 + 1 + 2 + 3 + + + 275 + EXCHANGE + + + 276 + MULTIPLESTRINGVALUE + A + B + C + D + E + F + G + H + I + L + J + K + M + N + O + P + Q + R + S + T + U + V + W + X + Y + Z + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + 0 + 1 + 2 + 1000 + 1001 + 1002 + 1003 + + + 277 + MULTIPLESTRINGVALUE + A + B + C + D + E + F + G + H + I + J + K + L + M + N + P + Q + R + Y + Z + S + T + U + V + W + X + 0 + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + AA + AB + AC + AD + AE + AF + AG + AH + AI + AJ + AK + AL + AM + AN + AO + AP + AQ + AR + AS + AT + 1000 + 1001 + 1002 + + + 278 + STRING + + + 279 + CHAR + 0 + 1 + 2 + 3 + 4 + + + 280 + STRING + + + 281 + CHAR + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + A + B + C + D + + + 282 + STRING + + + 283 + STRING + + + 284 + STRING + + + 285 + CHAR + 0 + 1 + + + 286 + MULTIPLECHARVALUE + 0 + 1 + 2 + 3 + 4 + 5 + + + 287 + INT + + + 288 + STRING + + + 289 + STRING + + + 290 + INT + + + 291 + MULTIPLECHARVALUE + 1 + 2 + 3 + + + 292 + MULTIPLECHARVALUE + A + B + C + D + E + F + G + H + I + J + K + L + M + N + O + P + Q + R + S + T + U + V + + + 293 + QTY + + + 294 + QTY + + + 295 + NUMINGROUP + + + 296 + NUMINGROUP + + + 297 + INT + 0 + 1 + 10 + 11 + 12 + 13 + 14 + 15 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 17 + + + 298 + INT + 1 + 2 + 3 + 4 + 5 + + + 299 + STRING + + + 300 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 99 + + + 301 + INT + 0 + 1 + 2 + + + 302 + STRING + + + 303 + INT + 1 + 2 + + + 304 + INT + + + 305 + STRING + + + 306 + STRING + + + 307 + STRING + + + 308 + EXCHANGE + + + 309 + STRING + + + 310 + STRING + + + 311 + STRING + + + 312 + STRING + + + 313 + MONTH-YEAR + + + 314 + DAY-OF-MONTH + + + 315 + INT + + + 316 + PRICE + + + 317 + CHAR + + + 318 + CURRENCY + + + 319 + QTY + + + 320 + STRING + + + 321 + INT + 0 + 1 + 2 + 3 + + + 322 + STRING + + + 323 + INT + 1 + 2 + 3 + 4 + 5 + 6 + + + 324 + STRING + + + 325 + BOOLEAN + + + 326 + INT + 1 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 2 + 20 + 21 + 22 + 23 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + + + 327 + CHAR + D + E + I + M + P + X + + + 328 + BOOLEAN + + + 329 + BOOLEAN + + + 330 + QTY + + + 331 + QTY + + + 332 + PRICE + + + 333 + PRICE + + + 334 + INT + 1 + 2 + 3 + + + 335 + STRING + + + 336 + STRING + + + 337 + STRING + + + 338 + INT + 1 + 2 + 3 + + + 339 + INT + 1 + 2 + 3 + + + 340 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + + + 341 + UTCTIMESTAMP + + + 342 + UTCTIMESTAMP + + + 343 + UTCTIMESTAMP + + + 344 + UTCTIMESTAMP + + + 345 + UTCTIMESTAMP + + + 346 + INT + + + 347 + STRING + ISO-2022-JP + EUC-JP + SHIFT_JIS + UTF-8 + + + 348 + LENGTH + + + 349 + DATA + + + 350 + LENGTH + + + 351 + DATA + + + 352 + LENGTH + + + 353 + DATA + + + 354 + LENGTH + + + 355 + DATA + + + 356 + LENGTH + + + 357 + DATA + + + 358 + LENGTH + + + 359 + DATA + + + 360 + LENGTH + + + 361 + DATA + + + 362 + LENGTH + + + 363 + DATA + + + 364 + LENGTH + + + 365 + DATA + + + 366 + PRICE + + + 367 + UTCTIMESTAMP + + + 368 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 99 + + + 369 + SEQNUM + + + 370 + UTCTIMESTAMP + + + 371 + INT + + + 372 + STRING + + + 373 + INT + 0 + 1 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 99 + 18 + + + 374 + CHAR + C + N + + + 375 + STRING + + + 376 + STRING + + + 377 + BOOLEAN + + + 378 + INT + 0 + 1 + 10 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 99 + 11 + 51 + 100 + + + 379 + STRING + + + 380 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 18 + + + 381 + AMT + + + 382 + NUMINGROUP + + + 383 + LENGTH + + + 384 + NUMINGROUP + + + 385 + CHAR + R + S + + + 386 + NUMINGROUP + + + 387 + QTY + + + 388 + CHAR + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + + + 389 + FLOAT + + + 390 + STRING + + + 391 + STRING + + + 392 + STRING + + + 393 + INT + + + 394 + INT + 1 + 2 + 3 + + + 395 + INT + + + 396 + AMT + + + 397 + AMT + + + 398 + NUMINGROUP + + + 399 + INT + 1 + 2 + 3 + + + 400 + STRING + + + 401 + INT + 1 + 2 + + + 402 + PERCENTAGE + + + 403 + PERCENTAGE + + + 404 + AMT + + + 405 + PERCENTAGE + + + 406 + AMT + + + 407 + PERCENTAGE + + + 408 + AMT + + + 409 + INT + 1 + 2 + 3 + 4 + + + 410 + PERCENTAGE + + + 411 + BOOLEAN + + + 412 + AMT + + + 413 + PERCENTAGE + + + 414 + INT + 1 + 2 + 3 + + + 415 + INT + + + 416 + INT + 1 + 2 + + + 417 + INT + + + 418 + CHAR + A + G + J + R + + + 419 + CHAR + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + A + B + C + D + Z + + + 420 + NUMINGROUP + + + 421 + COUNTRY + + + 422 + INT + + + 423 + INT + 1 + 10 + 11 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + + + 424 + QTY + + + 425 + QTY + + + 426 + PRICE + + + 427 + INT + 0 + 1 + 2 + + + 428 + NUMINGROUP + + + 429 + INT + 1 + 2 + 3 + 4 + 5 + 6 + + + 430 + INT + 1 + 2 + + + 431 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + + + 432 + LOCALMKTDATE + + + 433 + CHAR + 1 + 2 + 3 + 4 + 5 + + + 434 + CHAR + 1 + 2 + + + 435 + PERCENTAGE + + + 436 + FLOAT + + + 437 + QTY + + + 438 + UTCTIMESTAMP + + + 439 + STRING + + + 440 + STRING + + + 441 + INT + + + 442 + CHAR + 1 + 2 + 3 + + + 443 + UTCTIMESTAMP + + + 444 + STRING + + + 445 + LENGTH + + + 446 + DATA + + + 447 + CHAR + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + A + B + P + C + D + E + F + G + H + I + + + 448 + STRING + + + 449 + UTCDATEONLY + + + 450 + UTCTIMEONLY + + + 451 + PRICEOFFSET + + + 452 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 + 32 + 33 + 34 + 35 + 36 + 37 + 38 + 39 + 40 + 41 + 42 + 43 + 44 + 45 + 46 + 47 + 48 + 49 + 50 + 51 + 52 + 53 + 54 + 55 + 56 + 57 + 58 + 59 + 60 + 61 + 62 + 63 + 64 + 65 + 66 + 67 + 68 + 69 + 70 + 71 + 72 + 73 + 74 + 75 + 76 + 77 + 78 + 122 + + + 453 + NUMINGROUP + + + 454 + NUMINGROUP + + + 455 + STRING + + + 456 + STRING + + + 457 + NUMINGROUP + + + 458 + STRING + + + 459 + STRING + + + 460 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + + + 461 + STRING + + + 462 + INT + + + 463 + STRING + + + 464 + BOOLEAN + + + 465 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + + + 466 + STRING + + + 467 + STRING + + + 468 + CHAR + 0 + 1 + 2 + + + 469 + FLOAT + + + 470 + COUNTRY + + + 471 + STRING + + + 472 + STRING + + + 473 + NUMINGROUP + + + 474 + STRING + + + 475 + COUNTRY + + + 476 + STRING + + + 477 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + + + 478 + CURRENCY + + + 479 + CURRENCY + + + 480 + CHAR + N + M + O + Y + + + 481 + CHAR + Y + N + 1 + 2 + 3 + + + 482 + STRING + + + 483 + UTCTIMESTAMP + + + 484 + CHAR + B + C + D + E + O + P + Q + S + + + 485 + FLOAT + + + 486 + LOCALMKTDATE + + + 487 + INT + 0 + 1 + 2 + 3 + 4 + 5 + + + 488 + STRING + + + 489 + STRING + + + 490 + LOCALMKTDATE + + + 491 + STRING + + + 492 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + + + 493 + STRING + + + 494 + STRING + + + 495 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 999 + + + 496 + STRING + + + 497 + CHAR + N + Y + + + 498 + STRING + + + 499 + STRING + + + 500 + STRING + + + 501 + STRING + + + 502 + STRING + + + 503 + LOCALMKTDATE + + + 504 + LOCALMKTDATE + + + 505 + STRING + + + 506 + CHAR + A + R + H + N + + + 507 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 99 + + + 508 + STRING + + + 509 + STRING + + + 510 + NUMINGROUP + + + 511 + STRING + + + 512 + PERCENTAGE + + + 513 + STRING + + + 514 + CHAR + 0 + 1 + 2 + + + 515 + UTCTIMESTAMP + + + 516 + PERCENTAGE + + + 517 + CHAR + J + T + 2 + + + 518 + NUMINGROUP + + + 519 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + + + 520 + FLOAT + + + 521 + CURRENCY + + + 522 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + + + 523 + STRING + + + 524 + STRING + + + 525 + CHAR + + + 526 + STRING + + + 527 + STRING + + + 528 + CHAR + A + G + I + P + R + W + C + + + 529 + MULTIPLECHARVALUE + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + A + + + 530 + CHAR + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 9 + + + 531 + CHAR + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 9 + + + 532 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 99 + 100 + 101 + + + 533 + INT + + + 534 + INT + + + 535 + STRING + + + 536 + STRING + + + 537 + INT + 0 + 1 + 2 + 3 + + + 538 + INT + + + 539 + NUMINGROUP + + + 540 + AMT + + + 541 + LOCALMKTDATE + + + 542 + LOCALMKTDATE + + + 543 + STRING + + + 544 + CHAR + 1 + 2 + 3 + + + 545 + STRING + + + 546 + MULTIPLECHARVALUE + 1 + 2 + 3 + + + 547 + BOOLEAN + + + 548 + STRING + + + 549 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + + + 550 + INT + 0 + 1 + 2 + + + 551 + STRING + + + 552 + NUMINGROUP + 1 + 2 + + + 553 + STRING + + + 554 + STRING + + + 555 + NUMINGROUP + + + 556 + CURRENCY + + + 557 + INT + + + 558 + NUMINGROUP + + + 559 + INT + 0 + 1 + 2 + 3 + 4 + + + 560 + INT + 0 + 1 + 2 + 3 + 4 + 5 + + + 561 + QTY + + + 562 + QTY + + + 563 + INT + 0 + 1 + 2 + + + 564 + CHAR + + + 565 + INT + + + 566 + PRICE + + + 567 + INT + 1 + 99 + + + 568 + STRING + + + 569 + INT + 0 + 1 + 2 + 3 + 4 + + + 570 + BOOLEAN + + + 571 + STRING + + + 572 + STRING + + + 573 + CHAR + 0 + 1 + 2 + + + 574 + STRING + 1 + 2 + 3 + 4 + 5 + 6 + 7 + A1 + A2 + A3 + A4 + A5 + M3 + M4 + M5 + M6 + AQ + M1 + M2 + MT + S1 + S2 + S3 + S4 + S5 + 60 + 61 + 62 + 63 + 64 + 65 + + + 575 + BOOLEAN + + + 576 + NUMINGROUP + + + 577 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + + + 578 + STRING + + + 579 + STRING + + + 580 + INT + + + 581 + INT + 1 + 2 + 3 + 4 + 6 + 7 + 8 + + + 582 + INT + 1 + 2 + 3 + 4 + + + 583 + STRING + + + 584 + STRING + + + 585 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + + + 586 + UTCTIMESTAMP + + + 587 + CHAR + + + 588 + LOCALMKTDATE + + + 589 + CHAR + 0 + 1 + 2 + + + 590 + CHAR + 0 + 1 + 2 + + + 591 + CHAR + 0 + 1 + + + 592 + COUNTRY + + + 593 + STRING + + + 594 + STRING + + + 595 + STRING + + + 596 + COUNTRY + + + 597 + STRING + + + 598 + STRING + + + 599 + STRING + + + 600 + STRING + + + 601 + STRING + + + 602 + STRING + + + 603 + STRING + + + 604 + STRING + + + 605 + STRING + + + 606 + STRING + + + 607 + INT + + + 608 + STRING + + + 609 + STRING + + + 610 + MONTH-YEAR + + + 611 + LOCALMKTDATE + + + 612 + PRICE + + + 613 + CHAR + + + 614 + FLOAT + + + 615 + PERCENTAGE + + + 616 + EXCHANGE + + + 617 + STRING + + + 618 + LENGTH + + + 619 + DATA + + + 620 + STRING + + + 621 + LENGTH + + + 622 + DATA + + + 623 + FLOAT + + + 624 + CHAR + + + 625 + STRING + + + 626 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + + 627 + NUMINGROUP + + + 628 + STRING + + + 629 + UTCTIMESTAMP + + + 630 + SEQNUM + + + 631 + PRICE + + + 632 + PERCENTAGE + + + 633 + PERCENTAGE + + + 634 + PERCENTAGE + + + 635 + STRING + B + C + E + F + H + I + L + M + 1 + 2 + 3 + 4 + 5 + 9 + + + 636 + BOOLEAN + + + 637 + PRICE + + + 638 + INT + 0 + 1 + + + 639 + PRICEOFFSET + + + 640 + PRICE + + + 641 + PRICEOFFSET + + + 642 + PRICEOFFSET + + + 643 + PRICEOFFSET + + + 644 + STRING + + + 645 + PRICE + + + 646 + PRICE + + + 647 + QTY + + + 648 + QTY + + + 649 + STRING + + + 650 + BOOLEAN + + + 651 + PRICE + + + 652 + QTY + + + 653 + INT + 0 + 1 + 2 + 3 + 4 + + + 654 + STRING + + + 655 + STRING + + + 656 + FLOAT + + + 657 + FLOAT + + + 658 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 99 + + + 659 + STRING + + + 660 + INT + 1 + 2 + 3 + 4 + 5 + 99 + + + 661 + INT + + + 662 + PRICE + + + 663 + INT + + + 664 + STRING + + + 665 + INT + 1 + 2 + 3 + 4 + 5 + + + 666 + INT + 0 + 1 + 2 + + + 667 + MONTH-YEAR + + + 668 + INT + 1 + 2 + + + 669 + PRICE + + + 670 + NUMINGROUP + + + 671 + STRING + + + 672 + STRING + + + 673 + QTY + + + 674 + STRING + + + 675 + CURRENCY + + + 676 + CURRENCY + + + 677 + STRING + + + 678 + STRING + + + 679 + PRICE + + + 680 + INT + + + 681 + PRICE + + + 682 + STRING + + + 683 + NUMINGROUP + + + 684 + PRICE + + + 685 + QTY + + + 686 + INT + + + 687 + QTY + + + 688 + STRING + + + 689 + STRING + + + 690 + INT + 1 + 2 + 4 + 5 + + + 691 + STRING + + + 692 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + + + 693 + STRING + + + 694 + INT + 1 + 2 + 3 + 4 + 5 + 6 + + + 695 + CHAR + + + 696 + LOCALMKTDATE + + + 697 + PRICE + + + 698 + INT + + + 699 + STRING + + + 700 + BOOLEAN + + + 701 + LOCALMKTDATE + + + 702 + NUMINGROUP + + + 703 + STRING + TQ + IAS + IES + FIN + SOD + EX + AS + TX + TA + PIT + TRF + ETR + ALC + PA + ASF + DLV + TOT + XM + SPL + RCV + CAA + DN + EP + + + 704 + QTY + + + 705 + QTY + + + 706 + INT + 0 + 1 + 2 + + + 707 + STRING + FMTM + IMTM + TVAR + SMTM + PREM + CRES + CASH + VADJ + SETL + + + 708 + AMT + + + 709 + INT + 1 + 2 + 3 + 4 + 5 + 6 + + + 710 + STRING + + + 711 + NUMINGROUP + + + 712 + INT + 1 + 2 + 3 + 4 + + + 713 + STRING + + + 714 + STRING + + + 715 + LOCALMKTDATE + + + 716 + STRING + ITD + RTH + ETH + EOD + + + 717 + STRING + + + 718 + INT + 0 + 1 + 2 + 3 + + + 719 + BOOLEAN + + + 720 + BOOLEAN + + + 721 + STRING + + + 722 + INT + 0 + 1 + 2 + 3 + 4 + + + 723 + INT + 0 + 1 + 99 + + + 724 + INT + 0 + 1 + 2 + 3 + 4 + 5 + + + 725 + INT + 0 + 1 + + + 726 + STRING + + + 727 + INT + + + 728 + INT + 0 + 1 + 2 + 3 + 4 + 99 + + + 729 + INT + 0 + 1 + 2 + + + 730 + PRICE + + + 731 + INT + 1 + 2 + + + 732 + PRICE + + + 733 + INT + + + 734 + PRICE + + + 735 + NUMINGROUP + + + 736 + CURRENCY + + + 737 + AMT + + + 738 + AMT + + + 739 + LOCALMKTDATE + + + 740 + STRING + + + 741 + AMT + + + 742 + AMT + + + 743 + LOCALMKTDATE + + + 744 + CHAR + R + P + + + 745 + QTY + + + 746 + AMT + + + 747 + CHAR + A + M + + + 748 + INT + + + 749 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 8 + 9 + 99 + 100 + 200 + + + 750 + INT + 0 + 1 + 2 + + + 751 + INT + 0 + 1 + 2 + 3 + 4 + 99 + + + 752 + INT + 1 + 2 + 3 + + + 753 + NUMINGROUP + + + 754 + BOOLEAN + + + 755 + STRING + + + 756 + NUMINGROUP + + + 757 + STRING + + + 758 + CHAR + + + 759 + INT + + + 760 + STRING + + + 761 + STRING + + + 762 + STRING + + + 763 + STRING + + + 764 + STRING + + + 765 + PERCENTAGE + + + 766 + AMT + + + 767 + CURRENCY + + + 768 + NUMINGROUP + + + 769 + UTCTIMESTAMP + + + 770 + INT + 1 + 2 + 3 + 4 + 5 + 6 + + + 771 + STRING + + + 772 + STRING + + + 773 + INT + 1 + 2 + 3 + + + 774 + INT + 1 + 2 + 99 + + + 775 + INT + 0 + 1 + 2 + + + 776 + INT + + + 777 + STRING + + + 778 + NUMINGROUP + + + 779 + UTCTIMESTAMP + + + 780 + INT + 0 + 1 + 2 + 3 + 4 + + + 781 + NUMINGROUP + + + 782 + STRING + + + 783 + CHAR + + + 784 + INT + + + 785 + STRING + + + 786 + INT + + + 787 + CHAR + S + C + + + 788 + INT + 1 + 2 + 3 + 4 + + + 789 + SEQNUM + + + 790 + STRING + + + 791 + STRING + + + 792 + INT + 0 + 1 + 2 + 99 + + + 793 + STRING + + + 794 + INT + 3 + 4 + 5 + 8 + 2 + 9 + 10 + 11 + 12 + 14 + + + 795 + STRING + + + 796 + INT + 1 + 2 + 99 + + + 797 + BOOLEAN + + + 798 + INT + 1 + 2 + 3 + 4 + 6 + 7 + 8 + + + 799 + PRICE + + + 800 + QTY + + + 801 + NUMINGROUP + + + 802 + NUMINGROUP + + + 803 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 + 32 + + + 804 + NUMINGROUP + + + 805 + INT + + + 806 + NUMINGROUP + + + 807 + INT + + + 808 + INT + 1 + 2 + 3 + 4 + 5 + 6 + + + 810 + PRICE + + + 811 + FLOAT + + + 812 + INT + + + 813 + INT + + + 814 + INT + 0 + 1 + 2 + 3 + + + 815 + INT + 0 + 1 + 2 + 3 + + + 816 + NUMINGROUP + + + 817 + STRING + + + 818 + STRING + + + 819 + INT + 0 + 1 + 2 + + + 820 + STRING + + + 821 + STRING + + + 822 + STRING + + + 823 + STRING + + + 824 + STRING + + + 825 + STRING + + + 826 + INT + 0 + 1 + 2 + 3 + 4 + 5 + + + 827 + INT + 0 + 1 + + + 828 + INT + 0 + 1 + 10 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 + 32 + 33 + 34 + 35 + 36 + 37 + 38 + 39 + 40 + 41 + 42 + 43 + 44 + 45 + 46 + 47 + 54 + + + 829 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 + 32 + 1000 + 1004 + 1005 + 1006 + 1007 + 1008 + 1009 + 1010 + 1011 + 1012 + 1013 + 2001 + 3001 + 1018 + 1019 + 1020 + 1021 + 1022 + 1023 + 1024 + 1025 + 1026 + 1027 + 1028 + 1029 + 2002 + 1031 + 1032 + 1033 + + + 830 + STRING + + + 831 + STRING + + + 832 + INT + + + 833 + STRING + + + 834 + PRICEOFFSET + + + 835 + INT + 0 + 1 + + + 836 + INT + 0 + 1 + 2 + 3 + + + 837 + INT + 0 + 1 + 2 + + + 838 + INT + 1 + 2 + + + 839 + PRICE + + + 840 + INT + 1 + 2 + 3 + 4 + + + 841 + INT + 0 + 1 + + + 842 + INT + 0 + 1 + 2 + 3 + + + 843 + INT + 0 + 1 + 2 + + + 844 + INT + 1 + 2 + + + 845 + PRICE + + + 846 + INT + 1 + 2 + 3 + 4 + + + 847 + INT + 1 + 2 + 3 + + + 848 + STRING + + + 849 + PERCENTAGE + + + 850 + FLOAT + + + 851 + INT + 1 + 2 + 3 + + + 852 + BOOLEAN + + + 853 + INT + 0 + 1 + 2 + 3 + 4 + 5 + + + 854 + INT + 0 + 1 + 2 + + + 855 + INT + + + 856 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + + + 857 + INT + 0 + 1 + + + 858 + AMT + + + 859 + STRING + + + 860 + PRICE + + + 861 + PRICE + + + 862 + NUMINGROUP + + + 863 + QTY + + + 864 + NUMINGROUP + + + 865 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 99 + + + 866 + LOCALMKTDATE + + + 867 + PRICE + + + 868 + STRING + + + 869 + PERCENTAGE + + + 870 + NUMINGROUP + + + 871 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 99 + + + 872 + STRING + + + 873 + LOCALMKTDATE + + + 874 + LOCALMKTDATE + + + 875 + INT + 1 + 2 + 99 + + + 876 + STRING + + + 877 + STRING + + + 878 + STRING + + + 879 + QTY + + + 880 + STRING + + + 881 + STRING + + + 882 + PRICE + + + 883 + PRICE + + + 884 + AMT + + + 885 + AMT + + + 886 + AMT + + + 887 + NUMINGROUP + + + 888 + STRING + + + 889 + STRING + + + 890 + AMT + + + 891 + INT + 0 + 1 + 2 + + + 892 + INT + + + 893 + BOOLEAN + + + 894 + STRING + + + 895 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + + + 896 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + + + 897 + NUMINGROUP + + + 898 + PERCENTAGE + + + 899 + AMT + + + 900 + AMT + + + 901 + AMT + + + 902 + STRING + + + 903 + INT + 0 + 1 + 2 + 3 + 4 + + + 904 + STRING + + + 905 + INT + 0 + 1 + 2 + 3 + + + 906 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 99 + + + 907 + STRING + + + 908 + STRING + + + 909 + STRING + + + 910 + INT + 0 + 1 + 2 + 3 + 4 + + + 911 + INT + + + 912 + BOOLEAN + + + 913 + STRING + + + 914 + STRING + + + 915 + LOCALMKTDATE + + + 916 + LOCALMKTDATE + + + 917 + LOCALMKTDATE + + + 918 + CURRENCY + + + 919 + INT + 0 + 1 + 2 + 3 + + + 920 + AMT + + + 921 + AMT + + + 922 + AMT + + + 923 + STRING + + + 924 + INT + 1 + 2 + 3 + 4 + + + 925 + STRING + + + 926 + INT + 1 + 2 + 3 + 4 + 5 + 6 + + + 927 + STRING + + + 928 + INT + 1 + 2 + 3 + 4 + + + 929 + STRING + + + 930 + STRING + + + 931 + STRING + + + 932 + STRING + + + 933 + STRING + + + 934 + STRING + + + 935 + INT + 1 + 2 + 4 + 8 + + + 936 + NUMINGROUP + + + 937 + INT + 1 + 2 + + + 938 + NUMINGROUP + + + 939 + INT + 0 + 1 + 3 + + + 940 + INT + 1 + 2 + 3 + + + 941 + CURRENCY + + + 942 + CURRENCY + + + 943 + STRING + + + 944 + INT + 0 + 1 + 2 + + + 945 + INT + 0 + 1 + 2 + 3 + 4 + + + 946 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 99 + + + 947 + CURRENCY + + + 948 + NUMINGROUP + + + 949 + STRING + + + 950 + CHAR + + + 951 + INT + + + 952 + NUMINGROUP + + + 953 + STRING + + + 954 + INT + + + 955 + MONTH-YEAR + + + 956 + LOCALMKTDATE + + + 957 + NUMINGROUP + + + 958 + STRING + + + 959 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + + + 960 + STRING + + + 961 + STRING + + + 962 + UTCTIMESTAMP + + + 963 + INT + + + 964 + INT + + + 965 + STRING + 1 + 2 + + + 966 + STRING + + + 967 + FLOAT + + + 968 + FLOAT + + + 969 + FLOAT + + + 970 + INT + + + 971 + INT + + + 972 + PERCENTAGE + + + 973 + AMT + + + 974 + STRING + FIXED + DIFF + + + 975 + INT + 2 + 4 + 5 + + + 976 + LOCALMKTDATE + + + 977 + STRING + + + 978 + BOOLEAN + + + 979 + STRING + + + 980 + CHAR + A + D + M + + + 981 + NUMINGROUP + + + 982 + INT + 1 + 2 + 3 + 4 + 5 + + + 983 + QTY + + + 984 + NUMINGROUP + + + 985 + AMT + + + 986 + AMT + + + 987 + LOCALMKTDATE + + + 988 + STRING + + + 989 + STRING + + + 990 + STRING + + + 991 + PRICE + + + 992 + INT + 1 + 2 + + + 993 + STRING + + + 994 + STRING + + + 996 + STRING + MWh + MMBtu + Bbl + Gal + t + tn + MMbbl + lbs + oz_tr + USD + Bcf + Bu + + + 997 + STRING + S + Min + H + D + Wk + Mo + Yr + + + 998 + STRING + + + 999 + STRING + + + 1000 + STRING + + + 1001 + STRING + + + 1002 + INT + 1 + 2 + 3 + + + 1003 + STRING + + + 1005 + STRING + + + 1006 + STRING + + + 1007 + STRING + + + 1008 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + + + 1009 + INT + + + 1011 + STRING + + + 1012 + UTCTIMESTAMP + + + 1013 + INT + + + 1014 + STRING + + + 1015 + CHAR + 0 + 1 + + + 1016 + NUMINGROUP + + + 1017 + FLOAT + + + 1018 + NUMINGROUP + + + 1019 + STRING + + + 1020 + QTY + + + 1021 + INT + 1 + 2 + 3 + + + 1022 + STRING + + + 1023 + INT + + + 1024 + INT + 0 + 1 + 2 + + + 1025 + PRICE + + + 1026 + FLOAT + + + 1027 + PRICEOFFSET + + + 1028 + BOOLEAN + + + 1029 + BOOLEAN + + + 1030 + STRING + + + 1031 + MULTIPLESTRINGVALUE + ADD + AON + CNH + DIR + E.W + FOK + IO + IOC + LOO + LOC + MAO + MAC + MOO + MOC + MQT + NH + OVD + PEG + RSV + S.W + SCL + TMO + TS + WRK + + + 1032 + INT + 1 + + + 1033 + STRING + A + AR + D + IN + IS + O + PF + PR + PT + S + T + + + 1034 + INT + 1 + + + 1035 + MULTIPLESTRINGVALUE + ADD + AON + CNH + DIR + E.W + FOK + IO + IOC + LOO + LOC + MAO + MAC + MOO + MOC + MQT + NH + OVD + PEG + RSV + S.W + SCL + TMO + TS + WRK + + + 1036 + CHAR + 0 + 1 + 2 + + + 1037 + AMT + + + 1038 + AMT + + + 1039 + STRING + + + 1040 + STRING + + + 1041 + STRING + + + 1042 + STRING + + + 1043 + INT + 0 + 1 + + + 1044 + QTY + + + 1045 + FLOAT + + + 1046 + CHAR + M + D + + + 1047 + CHAR + O + C + R + F + + + 1048 + PRICEOFFSET + + + 1049 + CHAR + + + 1050 + CHAR + + + 1051 + INT + + + 1052 + NUMINGROUP + + + 1053 + STRING + + + 1054 + INT + + + 1055 + STRING + + + 1056 + QTY + + + 1057 + BOOLEAN + + + 1058 + NUMINGROUP + + + 1059 + STRING + + + 1060 + CHAR + + + 1061 + INT + + + 1062 + NUMINGROUP + + + 1063 + STRING + + + 1064 + INT + + + 1065 + PRICEOFFSET + + + 1066 + PRICEOFFSET + + + 1067 + PRICEOFFSET + + + 1068 + PRICEOFFSET + + + 1069 + PRICEOFFSET + + + 1070 + INT + 0 + 1 + 2 + 3 + 4 + + + 1071 + PRICEOFFSET + + + 1072 + AMT + + + 1073 + PRICEOFFSET + + + 1074 + QTY + + + 1075 + AMT + + + 1079 + TZTIMEONLY + + + 1080 + STRING + + + 1081 + CHAR + 0 + 1 + 2 + 3 + + + 1082 + QTY + + + 1083 + CHAR + 1 + 2 + + + 1084 + CHAR + 1 + 2 + 3 + 4 + + + 1085 + QTY + + + 1086 + QTY + + + 1087 + QTY + + + 1088 + QTY + + + 1089 + QTY + + + 1090 + INT + + + 1091 + BOOLEAN + + + 1092 + CHAR + 0 + 1 + 2 + 3 + + + 1093 + CHAR + 1 + 2 + 3 + + + 1094 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + + + 1095 + PRICE + + + 1096 + STRING + + + 1097 + STRING + + + 1098 + STRING + + + 1099 + STRING + + + 1100 + CHAR + 1 + 2 + 3 + 4 + + + 1101 + CHAR + 1 + 2 + 3 + + + 1102 + PRICE + + + 1103 + STRING + + + 1104 + STRING + + + 1105 + STRING + + + 1106 + STRING + + + 1107 + CHAR + 1 + 2 + 3 + 4 + 5 + 6 + + + 1108 + CHAR + 0 + 1 + 2 + 3 + + + 1109 + CHAR + U + D + + + 1110 + PRICE + + + 1111 + CHAR + 1 + 2 + + + 1112 + QTY + + + 1113 + STRING + + + 1114 + STRING + + + 1115 + CHAR + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + + + 1116 + NUMINGROUP + + + 1117 + STRING + + + 1118 + CHAR + + + 1119 + INT + + + 1120 + NUMINGROUP + + + 1121 + STRING + + + 1122 + INT + + + 1123 + CHAR + 0 + 1 + 2 + 3 + 4 + + + 1124 + CHAR + + + 1125 + LOCALMKTDATE + + + 1126 + STRING + + + 1127 + STRING + + + 1128 + STRING + 9 + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + + + 1129 + STRING + + + 1130 + STRING + + + 1131 + STRING + + + 1132 + TZTIMESTAMP + + + 1133 + CHAR + B + C + D + E + G + + + 1134 + BOOLEAN + + + 1135 + STRING + + + 1136 + STRING + + + 1137 + STRING + 9 + + + 1138 + QTY + + + 1139 + STRING + + + 5450 + INT + 1 + 2 + 11 + 12 + 45 + 46 + 47 + 48 + 49 + 50 + 51 + 52 + 53 + 54 + + + 8000 + UTCTIMESTAMP + + + 9000 + INT + + + 9001 + STRING + + + 9002 + STRING + + + 9995 + LOCALMKTDATE + + + 1167 + CHAR + 5 + + + 1166 + STRING + + + 1180 + STRING + + + 1181 + SEQNUM + + + 1182 + SEQNUM + + + 1183 + SEQNUM + + + 1300 + STRING + + + 1301 + STRING + + + 1328 + STRING + + + 1346 + STRING + + + 1347 + STRING + + + 1350 + SEQNUM + + + 1351 + NUMINGROUP + + + 1352 + BOOLEAN + + + 1353 + STRING + + + 1354 + INT + 0 + 1 + 2 + + + 1355 + STRING + + + 1357 + SEQNUM + + + 1369 + STRING + + + 1427 + STRING + + + 1444 + INT + 1 + 2 + 4 + + + 1461 + NUMINGROUP + + + 1462 + STRING + + + 1463 + CHAR + D + + + 1464 + INT + 1 + 76 + + + 9730 + CHAR + A + R + C + + + 20000 + CHAR + 0 + 1 + 2 + + + 20100 + PRICE + + + 20110 + INT + 0 + 1 + + + 20111 + INT + 0 + 1 + + + 27010 + INT + 0 + 99 + 100 + 1 + 2 + 3 + + + + + Group + 1461 + NUMINGROUP + + + + + + Group + 552 + NUMINGROUP + + + + + + + + + Group + 453 + NUMINGROUP + + + + + + Group + 552 + NUMINGROUP + + + + + + + Group + 453 + NUMINGROUP + + + + + + Group + 1461 + NUMINGROUP + + + + + + Group + 295 + NUMINGROUP + + + + + Group + 296 + NUMINGROUP + + + + + Group + 295 + NUMINGROUP + + + + + + + + Group + 454 + NUMINGROUP + + + + + Group + 552 + NUMINGROUP + + + + + + + + + + + + + + + + Group + 453 + NUMINGROUP + + + + + + Group + 454 + NUMINGROUP + + + + + Group + 453 + NUMINGROUP + + + + + + Group + 1351 + NUMINGROUP + + + + + + Group + 1351 + NUMINGROUP + + + + + + + + Group + 453 + NUMINGROUP + + + + + + Group + 453 + NUMINGROUP + + + + + + Group + 386 + NUMINGROUP + + + + Group + 73 + NUMINGROUP + + + + + + Group + 398 + NUMINGROUP + + + + Message + false + D + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Message + false + 8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Component + + + + Component + + + + Component + + + + Component + + + + Header + + + + + + + + + + + + + + + + Trailer + + + + Message + true + A + + + + + + + + + + + + + Message + true + 5 + + + + + + + Message + true + 0 + + + + + + Message + true + 1 + + + + + + Message + true + 2 + + + + + + + Message + true + 3 + + + + + + + + + + Message + true + 4 + + + + + + + diff --git a/src/main/kotlin/com/exactpro/th2/codec/fixng/ByteBufUtil.kt b/src/main/kotlin/com/exactpro/th2/codec/fixng/ByteBufUtil.kt new file mode 100644 index 0000000..8ecfe7f --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/codec/fixng/ByteBufUtil.kt @@ -0,0 +1,117 @@ +/* + * Copyright 2023-2024 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.exactpro.th2.codec.fixng + +import io.netty.buffer.ByteBuf +import java.nio.charset.Charset + +private const val SEP_BYTE = '='.code.toByte() +private const val SOH_BYTE = 1.toByte() +private const val DIGIT_0 = '0'.code.toByte() +private const val DIGIT_9 = '9'.code.toByte() + +private fun Int.getDigitCount(): Int = when { + this < 10 -> 1 + this < 100 -> 2 + this < 1000 -> 3 + this < 10000 -> 4 + this < 100000 -> 5 + this < 1000000 -> 6 + this < 10000000 -> 7 + this < 100000000 -> 8 + this < 1000000000 -> 9 + else -> 10 +} + +private fun ByteBuf.printInt(sourceValue: Int, digits: Int = sourceValue.getDigitCount()): ByteBuf = apply { + var value = sourceValue + + ensureWritable(digits) + + repeat(digits) { index -> + setByte(digits - index - 1 + writerIndex(), value % 10 + DIGIT_0) + value /= 10 + } + + writerIndex(writerIndex() + digits) +} + +fun ByteBuf.readTag(): Int { + val offset = readerIndex() + var tag = 0 + + while (isReadable) { + val byte = readByte() + + if (byte in DIGIT_0..DIGIT_9) { + tag = tag * 10 + byte - DIGIT_0 + if (tag >= 0) continue + } + + return if (byte == SEP_BYTE && tag != 0) tag else break + } + + error("No valid tag at offset: $offset") +} + +fun ByteBuf.writeTag(tag: Int): ByteBuf { + require(tag > 0) { "Tag cannot be negative" } + return printInt(tag).writeByte(SEP_BYTE.toInt()) +} + +fun ByteBuf.readValue(charset: Charset, isDirty: Boolean): String { + val offset = readerIndex() + val length = bytesBefore(SOH_BYTE) + check(isDirty || length > 0) { "No valid value at offset: $offset" } + readerIndex(offset + length + 1) + return toString(offset, length, charset) +} + +fun ByteBuf.writeValue(value: String, charset: Charset): ByteBuf = apply { + writeCharSequence(value, charset) + writeByte(SOH_BYTE.toInt()) +} + +inline fun ByteBuf.forEachField( + charset: Charset, + isDirty: Boolean, + action: (tag: Int, value: String) -> Boolean, +) { + while (isReadable) { + val offset = readerIndex() + if (action(readTag(), readValue(charset, isDirty))) continue + readerIndex(offset) + break + } +} + +inline fun ByteBuf.readField(tag: Int, charset: Charset, isDirty: Boolean, message: (Int) -> String): String = readTag().let { + check(it == tag) { message(it) } + readValue(charset, isDirty) +} + +fun ByteBuf.writeField(tag: Int, value: String, charset: Charset): ByteBuf = writeTag(tag).writeValue(value, charset) + +fun ByteBuf.writeField(tag: Int, value: Any?, charset: Charset): ByteBuf = writeField(tag, value.toString(), charset) + +fun ByteBuf.writeChecksum() { + val index = readerIndex() + var checksum = 0 + while (isReadable) checksum += readByte() + readerIndex(index) + writeTag(10).printInt(checksum % 256, 3).writeByte(SOH_BYTE.toInt()) +} \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/fixng/FixNgCodec.kt b/src/main/kotlin/com/exactpro/th2/codec/fixng/FixNgCodec.kt new file mode 100644 index 0000000..bb044be --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/codec/fixng/FixNgCodec.kt @@ -0,0 +1,647 @@ +/* + * Copyright 2023-2024 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.exactpro.th2.codec.fixng + +import com.exactpro.sf.common.impl.messages.xml.configuration.JavaType +import com.exactpro.sf.common.messages.structures.DictionaryConstants.FIELD_MESSAGE_TYPE +import com.exactpro.sf.common.messages.structures.IAttributeStructure +import com.exactpro.sf.common.messages.structures.IDictionaryStructure +import com.exactpro.sf.common.messages.structures.IFieldStructure +import com.exactpro.sf.common.messages.structures.IMessageStructure +import com.exactpro.sf.common.messages.structures.StructureUtils +import com.exactpro.th2.codec.api.IPipelineCodec +import com.exactpro.th2.codec.api.IReportingContext +import com.exactpro.th2.codec.fixng.FixNgCodecFactory.Companion.PROTOCOL +import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.MessageGroup +import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.ParsedMessage +import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.RawMessage +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled +import java.math.BigDecimal +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.format.DateTimeFormatter +import java.time.format.DateTimeFormatterBuilder +import java.time.format.DateTimeParseException +import java.time.temporal.ChronoField +import java.util.EnumMap +import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.Message as CommonMessage + +class FixNgCodec(dictionary: IDictionaryStructure, settings: FixNgCodecSettings) : IPipelineCodec { + private val beginString = settings.beginString + private val charset = settings.charset + private val isDirtyMode = settings.dirtyMode + private val isDecodeToStrings = settings.decodeValuesToStrings + private val isDecodeComponentsToNestedMaps = settings.decodeComponentsToNestedMaps + + private val fieldsEncode = convertToFieldsByName(dictionary.fields, true, emptyList(), true) + private val fieldsDecode = convertToFieldsByTag(dictionary.fields, emptyList()) + private val messagesByTypeForDecode: Map + private val messagesByNameForEncode: Map + + private val headerDef: Message + private val trailerDef: Message + + init { + val messagesForEncode = dictionary.toMessages(isForEncode = true) + val messagesForDecode = dictionary.toMessages(isForEncode = false) + messagesByNameForEncode = messagesForEncode.associateBy(Message::name) + messagesByTypeForDecode = messagesForDecode.associateBy(Message::type) + + val messagesByNameForDecode = messagesForDecode.associateBy(Message::name) + headerDef = messagesByNameForDecode[HEADER] ?: error("Header is not defined in dictionary") + trailerDef = messagesByNameForDecode[TRAILER] ?: error("Trailer is not defined in dictionary") + } + + override fun encode(messageGroup: MessageGroup, context: IReportingContext): MessageGroup { + val messages = mutableListOf>() + + for (message in messageGroup.messages) { + if (message !is ParsedMessage || message.protocol.run { isNotEmpty() && this != PROTOCOL }) { + messages += message + continue + } + + val isDirty = isDirtyMode || (message.metadata[ENCODE_MODE_PROPERTY_NAME] == DIRTY_ENCODE_MODE) + val messageDef = messagesByNameForEncode[message.type] ?: error("Unknown message name: ${message.type}") + + val messageFields = message.body + @Suppress("UNCHECKED_CAST") + val headerFields = messageFields[HEADER] as? Map ?: mapOf() + @Suppress("UNCHECKED_CAST") + val trailerFields = messageFields[TRAILER] as? Map ?: mapOf() + + val body = Unpooled.buffer(1024) + val prefix = Unpooled.buffer(32) + + prefix.writeField(TAG_BEGIN_STRING, beginString, charset) + body.writeField(TAG_MSG_TYPE, messageDef.type, charset) + + headerDef.encode(headerFields, body, isDirty, fieldsEncode, context) + messageDef.encode(messageFields, body, isDirty, fieldsEncode, context, FIELDS_NOT_IN_BODY) + trailerDef.encode(trailerFields, body, isDirty, fieldsEncode, context) + + prefix.writeField(TAG_BODY_LENGTH, body.readableBytes(), charset) + val buffer = Unpooled.wrappedBuffer(prefix, body) + buffer.writeChecksum() + + messages += RawMessage( + id = message.id, + eventId = message.eventId, + metadata = message.metadata, + protocol = PROTOCOL, + body = buffer + ) + } + + return MessageGroup(messages) + } + + override fun decode(messageGroup: MessageGroup, context: IReportingContext): MessageGroup { + val messages = mutableListOf>() + + for (message in messageGroup.messages) { + if (message !is RawMessage || message.protocol.run { isNotEmpty() && this != PROTOCOL }) { + messages += message + continue + } + + val isDirty = isDirtyMode || (message.metadata[ENCODE_MODE_PROPERTY_NAME] == DIRTY_ENCODE_MODE) + val buffer = message.body + + val beginString = buffer.readField(TAG_BEGIN_STRING, charset, isDirty) { "Message starts with $it tag instead of BeginString ($TAG_BEGIN_STRING)" } + val bodyLengthString = buffer.readField(TAG_BODY_LENGTH, charset, isDirty) { "BeginString ($TAG_BEGIN_STRING) is followed by $it tag instead of BodyLength ($TAG_BODY_LENGTH)" } + val bodyLength = bodyLengthString.toIntOrNull() ?: handleError(isDirty, context, "Wrong number value in integer field 'BodyLength'. Value: $bodyLengthString.", bodyLengthString) + val msgType = buffer.readField(TAG_MSG_TYPE, charset, isDirty) { "BodyLength ($TAG_BODY_LENGTH) is followed by $it tag instead of MsgType ($TAG_MSG_TYPE)" } + val messageDef = messagesByTypeForDecode[msgType] ?: error("Unknown message type: $msgType") + + val header = headerDef.decode(buffer, messageDef, isDirty, fieldsDecode, context) + val body = messageDef.decode(buffer, messageDef, isDirty, fieldsDecode, context) + val trailer = trailerDef.decode(buffer, messageDef, isDirty, fieldsDecode, context) + + if (buffer.isReadable) { + // this should never happen in dirty mode + val errorMessage = if (isDirty) "Field was not processed in dirty mode. Tag: ${buffer.readTag()}" else "Tag appears out of order: ${buffer.readTag()}" + error(errorMessage) + } + + header["BeginString"] = beginString + header["BodyLength"] = if (isDecodeToStrings) bodyLengthString else bodyLength + header["MsgType"] = msgType + + body[HEADER] = header + body[TRAILER] = trailer + + messages += ParsedMessage( + id = message.id, + eventId = message.eventId, + metadata = message.metadata, + protocol = PROTOCOL, + type = messageDef.name, + body = body, + ) + } + + return MessageGroup(messages) + } + + private fun handleError(isDirty: Boolean, context: IReportingContext, errorMessageText: String, value: Any = Unit) = if (isDirty) { + context.warning(DIRTY_MODE_WARNING_PREFIX + errorMessageText) + value + } else { + error(errorMessageText) + } + + private fun Field.decode( + source: ByteBuf, + target: MutableMap, + tagsSet: MutableSet, + value: String, + tag: Int, + isDirty: Boolean, + context: IReportingContext + ) { + val decodedValue: Any = when { + value.isEmpty() -> handleError(isDirty, context, "Empty value in the field '$name'.", value) + containsNonPrintableChars(value) -> handleError(isDirty, context, "Non printable characters in the field '$name'. Value: $value", value) + else -> when (this) { + is Primitive -> decode(value, isDirty, context) + is Group -> decode( + source, + value.toIntOrNull() ?: error("Invalid $name group counter ($tag) value: $value"), + isDirty, + context + ) + + else -> error("Unsupported field type: $this") + } + } + + if (!tagsSet.add(tag)) { + // we always handle tag duplication as error (isDirty = false) + handleError(false, context, "Duplicate $name field ($tag) with value: $value", value) + } + + val targetMap = if (isDecodeComponentsToNestedMaps) { + @Suppress("UNCHECKED_CAST") + path.fold(target) { map, key -> map.computeIfAbsent(key) { mutableMapOf() } as MutableMap } + } else { + target + } + + targetMap[name] = decodedValue + } + + private val prereadHeaderTags = arrayOf(8 /* BeginString */, 9 /* BodyLength */, 35 /* MsgType */) + + private fun Message.decode(source: ByteBuf, bodyDef: Message, isDirty: Boolean, dictionaryFields: Map, context: IReportingContext): MutableMap = mutableMapOf().also { map -> + val tagsSet: MutableSet = hashSetOf(*prereadHeaderTags) + val usedComponents = mutableSetOf() + + source.forEachField(charset, isDirty) { tag, value -> + val field = get(tag) ?: if (isDirty) { + when (this) { + headerDef -> bodyDef[tag] ?: trailerDef[tag] + trailerDef -> null + else -> trailerDef[tag] + }?.let { return@forEachField false } // we reached next part of the message + + val dictField = dictionaryFields[tag] + if (dictField != null) { + context.warning(DIRTY_MODE_WARNING_PREFIX + "Unexpected field in message. Field name: ${dictField.name}. Field value: $value.") + dictField + } else { + context.warning(DIRTY_MODE_WARNING_PREFIX + "Field does not exist in dictionary. Field tag: $tag. Field value: $value.") + Primitive(false, tag.toString(), emptyList(), String::class.java, emptySet(), tag) + } + } else { + // we reached next part of the message + return@forEachField false + } + + usedComponents.addAll(field.path) + + field.decode(source, map, tagsSet, value, tag, isDirty, context) + return@forEachField true + } + + validateRequiredTags(requiredTags, tagsSet, isDirty, context) + for (componentName in usedComponents) { + val requiredTags = conditionallyRequiredTags[componentName] ?: continue + validateRequiredTags(requiredTags, tagsSet, isDirty, context) + } + } + + private fun validateRequiredTags(requiredTags: Set, tagsSet: Set, isDirty: Boolean, context: IReportingContext) { + for (tag in requiredTags) { + if (!tagsSet.contains(tag)) { + handleError(isDirty, context, "Required tag missing. Tag: $tag.") + } + } + } + + private fun Primitive.decode(value: String, isDirty: Boolean, context: IReportingContext): Any { + if (values.isNotEmpty() && !values.contains(value)) { + handleError(isDirty, context, "Invalid value in enum field $name. Actual: $value. Valid values $values.") + } + + val decodedValue = try { + when (primitiveType) { + java.lang.String::class.java -> value + java.lang.Character::class.java -> { + if (value.length != 1) { + handleError(isDirty, context, "Wrong value in character field '$name'. Value: $value", value) + } else { + value[0] + } + } + + java.lang.Integer::class.java -> value.toInt() + java.math.BigDecimal::class.java -> value.toBigDecimal() + java.lang.Long::class.java -> value.toLong() + java.lang.Short::class.java -> value.toShort() + java.lang.Byte::class.java -> value.toByte() + java.lang.Float::class.java -> value.toFloat() + java.lang.Double::class.java -> value.toDouble() + + java.time.LocalDateTime::class.java -> LocalDateTime.parse(value, dateTimeFormatter) + java.time.LocalDate::class.java -> LocalDate.parse(value, dateFormatter) + java.time.LocalTime::class.java -> LocalTime.parse(value, timeFormatter) + + java.lang.Boolean::class.java -> when (value) { + "Y" -> true + "N" -> false + else -> handleError(isDirty, context, "Wrong value in boolean field '$name'. Value: $value.", value) + } + + else -> error("Unsupported type: $primitiveType.") + } + } catch (e: NumberFormatException) { + handleError(isDirty, context, "Wrong number value in ${primitiveType.name} field '$name'. Value: $value.", value) + } catch (e: DateTimeParseException) { + handleError(isDirty, context, "Wrong date/time value in ${primitiveType.name} field '$name'. Value: $value.", value) + } + + return if (isDecodeToStrings) { + if (primitiveType == java.time.LocalDateTime::class.java || primitiveType == java.time.LocalDate::class.java || primitiveType == java.time.LocalTime::class.java || primitiveType == java.lang.Boolean::class.java) { + decodedValue.toString() + } else { + value + } + } else { + decodedValue + } + } + + private fun Group.decode(source: ByteBuf, count: Int, isDirty: Boolean, context: IReportingContext): List> = ArrayList>().also { list -> + var map: MutableMap? = null + val tags: MutableSet = hashSetOf() + + source.forEachField(charset, isDirty) { tag, value -> + val field = get(tag) ?: return@forEachField false + + val group = if (tag == delimiter || tags.contains(tag) || map == null) { + if (tag != delimiter) { + handleError(isDirty, context, "Field ${field.name} ($tag) appears before delimiter ($delimiter)") + } + + tags.clear() + mutableMapOf().also { + list.add(it) + map = it + } + } else { + map ?: error("Group entry map can't be null.") + } + + field.decode(source, group, tags, value, tag, isDirty, context) + return@forEachField true + } + + if (list.size != count) { + val errorText = "Unexpected group $name count: ${list.size} (expected: $count)" + handleError(isDirty, context, errorText) + } + } + + private fun encodeField(field: Field, value: Any, target: ByteBuf, isDirty: Boolean, dictionaryFields: Map, context: IReportingContext) { + when { + field is Primitive -> { + val valueToEncode = when { + isCompatibleType(value.javaClass, field.primitiveType) -> value + value is String -> { + try { + when (field.primitiveType) { + LocalDateTime::class.java -> LocalDateTime.parse(value) + LocalDate::class.java -> LocalDate.parse(value) + LocalTime::class.java -> LocalTime.parse(value) + java.lang.Boolean::class.java -> when { + value.equals("true", true) -> true + value.equals("false", true) -> false + else -> handleError(isDirty, context, "Wrong boolean value in ${field.primitiveType.name} field '$field.name'. Value: $value.", value) + } + else -> { + // we reuse decode() method for the types that have the same string representation + // of values in FIX protocol and in TH2 transport protocol + field.decode(value, isDirty, context) // validate if String value could be parsed to required type + target.writeField(field.tag, value, charset) + return + } + } + } catch (e: DateTimeParseException) { + handleError(isDirty, context, "Wrong date/time value in ${field.primitiveType.name} field '$field.name'. Value: $value.", value) + } + } + else -> handleError(isDirty, context, "Wrong type value in field ${field.name}. Actual: ${value.javaClass} (value: $value). Expected ${field.primitiveType}", value) + } + + val stringValue = when (valueToEncode) { + is LocalDateTime -> valueToEncode.format(dateTimeFormatter) + is LocalDate -> valueToEncode.format(dateFormatter) + is LocalTime -> valueToEncode.format(timeFormatter) + is java.lang.Boolean -> if (valueToEncode.booleanValue()) "Y" else "N" + else -> valueToEncode.toString() + } + + when { + stringValue.isEmpty() -> handleError(isDirty, context, "Empty value in the field '${field.name}'.") + containsNonPrintableChars(stringValue) -> handleError(isDirty, context, "Non-printable characters in the field '${field.name}'. Value: $value") + field.values.isNotEmpty() && !field.values.contains(stringValue) -> handleError(isDirty, context, "Invalid value in enum field ${field.name}. Actual: $value. Valid values ${field.values}.") + } + + target.writeField(field.tag, stringValue, charset) + } + + field is Group && value is List<*> -> field.encode(value, target, isDirty, dictionaryFields, context) + field is Message && value is Map<*,*> -> { + @Suppress("UNCHECKED_CAST") + val messageValue = value as Map + field.encode(messageValue, target, isDirty, dictionaryFields, context) + } + else -> error("Unsupported value in ${field.name} field: $value") + } + } + + private fun FieldMap.encode(source: Map, target: ByteBuf, isDirty: Boolean, dictionaryFields: Map, context: IReportingContext, fieldsToSkip: Set = emptySet()) { + fields.forEach { (name, field) -> + if (field is Primitive && field.tag in calculatedFields) return@forEach + val value = source[name] + if (value != null) { + encodeField(field, value, target, isDirty, dictionaryFields, context) + } else if (field.isRequired && this !== headerDef) { + handleError(isDirty, context, "Required field missing. Field name: $name.") + } + } + + source.filter { fields[it.key] == null && it.key !in fieldsToSkip}.forEach { (fieldName, value) -> + if (!isDirty) { + error("Unexpected field in message. Field name: $fieldName. Field value: $value. Message body: $source") + } + + val field = dictionaryFields[fieldName] + + if (field != null) { + context.warning(DIRTY_MODE_WARNING_PREFIX + "Unexpected field in message. Field name: $fieldName. Field value: $value. Message body: $source") + encodeField(field, value ?: "", target, true, dictionaryFields, context) + } else { + val tag = fieldName.toIntOrNull() + if(tag != null && tag > 0) { + if (value is List<*>) { // TODO: do we need this check? + error("List value with unspecified name. tag = $tag") + } else { + context.warning(DIRTY_MODE_WARNING_PREFIX + "Tag instead of field name. Field name: $fieldName. Field value: $value. Message body: $source") + target.writeField(tag, value, charset) + } + } else { + error("Field does not exist in dictionary. Field name: $fieldName. Field value: $value. Message body: $source") + } + } + } + } + + private fun Group.encode(source: List<*>, target: ByteBuf, isDirty: Boolean, dictionaryFields: Map, context: IReportingContext) { + target.writeField(counter, source.size, charset) + + source.forEach { group -> + check(group is Map<*, *>) { "Unsupported value in $name group: $group" } + @Suppress("UNCHECKED_CAST") + val groupMap = group as Map + encode(groupMap, target, isDirty, dictionaryFields, context) + } + } + + interface Field { + val isRequired: Boolean + val name: String + val path: List + } + + data class Primitive( + override val isRequired: Boolean, + override val name: String, + override val path: List, + val primitiveType: Class<*>, + val values: Set, + val tag: Int + ) : Field + + abstract class FieldMap { + abstract val name: String + abstract val fields: Map + + private val tags: Map by lazy { + fields.values.associateBy { field -> + when (field) { + is Primitive -> field.tag + is Group -> field.counter + else -> error("Field map $name contains unsupported field: $field") + } + } + } + + operator fun get(tag: Int): Field? = tags[tag] + operator fun get(field: String): Field? = fields[field] + } + + data class Message( + override val isRequired: Boolean, + override val name: String, + override val path: List, + val type: String, + override val fields: Map, + val requiredTags: Set, + val conditionallyRequiredTags: Map> + ) : Field, FieldMap() + + data class Group( + override val isRequired: Boolean, + override val name: String, + override val path: List, + val counter: Int, + val delimiter: Int, + override val fields: Map, + ) : Field, FieldMap() + + companion object { + private const val HEADER = "header" + private const val TRAILER = "trailer" + private val FIELDS_NOT_IN_BODY = setOf(HEADER, TRAILER) + private const val ENCODE_MODE_PROPERTY_NAME = "encode-mode" + private const val DIRTY_ENCODE_MODE = "dirty" + private const val TAG_BEGIN_STRING = 8 + private const val TAG_BODY_LENGTH = 9 + private const val TAG_CHECKSUM = 10 + private const val TAG_MSG_TYPE = 35 + private const val DIRTY_MODE_WARNING_PREFIX = "Dirty mode WARNING: " + + private fun containsNonPrintableChars(stringValue: String) = stringValue.any { it !in ' ' .. '~' } + private val calculatedFields = intArrayOf(TAG_BEGIN_STRING, TAG_BODY_LENGTH, TAG_MSG_TYPE, TAG_CHECKSUM) + + private val dateTimeFormatter = DateTimeFormatterBuilder() + .appendPattern("yyyyMMdd-HH:mm:ss") + .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true) + .toFormatter() + + private val dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd") + + private val timeFormatter = DateTimeFormatterBuilder() + .appendPattern("HH:mm:ss") + .appendFraction(ChronoField.MILLI_OF_SECOND, 0, 9, true) + .toFormatter() + + private val javaTypeToClass = EnumMap>(JavaType::class.java).apply { + for (type in JavaType.values()) { + put(type, Class.forName(type.value())) + } + withDefault { error("Unsupported java type: $it") } + } + + private val typeSizes = mapOf( + java.lang.Byte::class.java to 1, + java.lang.Short::class.java to 2, + java.lang.Integer::class.java to 3, + java.lang.Long::class.java to 4, + BigDecimal:: class.java to 5 + ) + + private fun isCompatibleType(from: Class<*>, to: Class<*>): Boolean { + if (from == to) return true + val fromSize = typeSizes[from] ?: return false + val toSize = typeSizes[to] ?: return false + return fromSize < toSize + } + + private val IMessageStructure.entityType: String + get() = StructureUtils.getAttributeValue(this, "entity_type") + + private val IMessageStructure.isGroup: Boolean + get() = entityType == "Group" + + private val IMessageStructure.isComponent: Boolean + get() = entityType == "Component" + + private val IFieldStructure.tag: Int + get() = StructureUtils.getAttributeValue(this, "tag") + + private fun IFieldStructure.toPrimitive(path: List, isRequiredParent: Boolean): Primitive = Primitive( + isRequiredParent && isRequired, + name, + path, + javaTypeToClass.getValue(javaType), + values.values.map { it.getCastValue().toString() }.toSet(), + tag + ) + + private fun convertToFieldsByName(fields: Map, isForEncode: Boolean, path: List, isRequiredParent: Boolean): Map = linkedMapOf().apply { + fields.forEach { (name, field) -> + when { + field !is IMessageStructure -> this[name] = field.toPrimitive(path, isForEncode || isRequiredParent) + field.isGroup -> this[name] = field.toGroup(isForEncode, path) + field.isComponent -> if (isForEncode) { + this[name] = field.toMessage(true, path + name) + } else { + this += convertToFieldsByName(field.fields, false, path + name, isRequiredParent && field.isRequired) + } + } + } + } + + private fun convertToFieldsByTag(fields: Map, path: List): Map = linkedMapOf().apply { + fields.values.forEach { field -> + when { + field !is IMessageStructure -> this[field.tag] = field.toPrimitive(path, true) + field.isGroup -> this[field.tag] = field.toGroup(false, path) + field.isComponent -> this += convertToFieldsByTag(field.fields, path + field.name) + } + } + } + + private fun collectRequiredTags(fields: Map, target: MutableSet): Set { + for (field in fields.values) { + when { + !field.isRequired -> continue + field !is IMessageStructure -> target.add(field.tag) + field.isGroup -> target.add(field.tag) + field.isComponent -> collectRequiredTags(field.fields, target) + } + } + return target + } + + private fun collectConditionallyRequiredTags(fields: Map, target: MutableMap>): Map> { + for (field in fields.values) { + if (field is IMessageStructure && field.isComponent) { + val isCurrentRequired = field.isRequired + // There is no point in adding tags from optional components that contain only one field + // (such a field is effectively optional even if it has a required flag). + if (!isCurrentRequired && field.fields.size > 1) { + target[field.name] = collectRequiredTags(field.fields, mutableSetOf()) + } + } + } + return target + } + + private fun IMessageStructure.toMessage(isForEncode: Boolean, path: List): Message = Message( + name = name, + type = StructureUtils.getAttributeValue(this, FIELD_MESSAGE_TYPE) ?: name, + fields = convertToFieldsByName(this.fields, isForEncode, path, !isComponent || isRequired), + path = path, + isRequired = isRequired, + requiredTags = if (isForEncode) emptySet() else collectRequiredTags(fields, mutableSetOf()), + conditionallyRequiredTags = if (isForEncode) emptyMap() else collectConditionallyRequiredTags(fields, mutableMapOf()) + ) + + private fun getFirstTag(message: IMessageStructure): Int = message.fields.values.first().let { + if (it is IMessageStructure && it.isComponent) getFirstTag(it) else it.tag + } + + private fun IMessageStructure.toGroup(isForEncode: Boolean, path: List): Group = Group( + name = name, + counter = tag, + delimiter = getFirstTag(this), + fields = convertToFieldsByName(this.fields, isForEncode, emptyList(), true), + path = path, + isRequired = isRequired + ) + + fun IDictionaryStructure.toMessages(isForEncode: Boolean): List = messages.values + .filterNot { it.isGroup || it.isComponent } + .map { it.toMessage(isForEncode, emptyList()) } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecFactory.kt b/src/main/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecFactory.kt new file mode 100644 index 0000000..c1a5c76 --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecFactory.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2023-2024 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.exactpro.th2.codec.fixng + +import com.exactpro.sf.common.messages.structures.loaders.XmlDictionaryStructureLoader +import com.exactpro.th2.codec.api.IPipelineCodec +import com.exactpro.th2.codec.api.IPipelineCodecContext +import com.exactpro.th2.codec.api.IPipelineCodecFactory +import com.exactpro.th2.codec.api.IPipelineCodecSettings +import com.google.auto.service.AutoService + +@AutoService(IPipelineCodecFactory::class) +class FixNgCodecFactory : IPipelineCodecFactory { + private lateinit var context: IPipelineCodecContext + override val protocols: Set + get() = PROTOCOLS + + override val settingsClass: Class = FixNgCodecSettings::class.java + + override fun init(pipelineCodecContext: IPipelineCodecContext) { + this.context = pipelineCodecContext + } + + override fun create(settings: IPipelineCodecSettings?): IPipelineCodec { + check(::context.isInitialized) { "'codecContext' was not loaded" } + val codecSettings = requireNotNull(settings as? FixNgCodecSettings) { + "settings is not an instance of ${FixNgCodecSettings::class.java}: ${settings?.let { it::class.java }}" + } + return FixNgCodec( + context[codecSettings.dictionary].use(XmlDictionaryStructureLoader()::load), + requireNotNull(codecSettings as? FixNgCodecSettings) { + "settings is not an instance of ${FixNgCodecSettings::class.java}: $codecSettings" + } + ) + } + + companion object { + const val PROTOCOL = "fix" + private val PROTOCOLS = setOf(PROTOCOL) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecSettings.kt b/src/main/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecSettings.kt new file mode 100644 index 0000000..3552807 --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecSettings.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2023-2024 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.exactpro.th2.codec.fixng + +import com.exactpro.th2.codec.api.IPipelineCodecSettings +import java.nio.charset.Charset + +data class FixNgCodecSettings( + val beginString: String = "FIXT.1.1", + val dictionary: String, + val charset: Charset = Charsets.US_ASCII, + val dirtyMode: Boolean = false, + val decodeValuesToStrings: Boolean = true, + val decodeComponentsToNestedMaps: Boolean = true +) : IPipelineCodecSettings \ No newline at end of file diff --git a/src/test/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecTest.kt b/src/test/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecTest.kt new file mode 100644 index 0000000..138170d --- /dev/null +++ b/src/test/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecTest.kt @@ -0,0 +1,736 @@ +/* + * Copyright 2023-2024 Exactpro (Exactpro Systems Limited) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.exactpro.th2.codec.fixng + +import com.exactpro.sf.common.messages.structures.IDictionaryStructure +import com.exactpro.sf.common.messages.structures.loaders.XmlDictionaryStructureLoader +import com.exactpro.th2.codec.api.IReportingContext +import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.RawMessage +import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.ParsedMessage +import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.MessageGroup +import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.MessageId +import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.EventId +import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.Direction +import com.exactpro.th2.codec.fixng.FixNgCodecFactory.Companion.PROTOCOL +import io.netty.buffer.CompositeByteBuf +import io.netty.buffer.Unpooled +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import java.math.BigDecimal +import java.nio.charset.StandardCharsets +import java.time.Instant +import java.time.LocalDateTime + +class FixNgCodecTest { + private val dictionary: IDictionaryStructure = FixNgCodecTest::class.java.classLoader + .getResourceAsStream("dictionary.xml") + .use(XmlDictionaryStructureLoader()::load) + + private val codec = FixNgCodec(dictionary, FixNgCodecSettings(dictionary = "", decodeValuesToStrings = false)) + + private val reportingContext = object : IReportingContext { + private val _warnings: MutableList = ArrayList() + + val warnings: List + get() = _warnings + + override fun warning(message: String) { + _warnings.add(message) + } + + override fun warnings(messages: Iterable) { + _warnings.addAll(messages) + } + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `simple encode`(isDirty: Boolean) = encodeTest(MSG_CORRECT, dirtyMode = isDirty) + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `simple encode from string values`(isDirty: Boolean) = encodeTest(MSG_CORRECT, dirtyMode = isDirty, encodeFromStringValues = true) + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `simple decode`(isDirty: Boolean) = decodeTest(MSG_CORRECT, dirtyMode = isDirty) + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `simple decode to string values`(isDirty: Boolean) = decodeTest(MSG_CORRECT, decodeToStringValues = true, dirtyMode = isDirty) + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `simple decode with no body`(isDirty: Boolean) = decodeTest(MSG_CORRECT_WITHOUT_BODY, expectedMessage = expectedMessageWithoutBody, dirtyMode = isDirty) + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `encode with addition field that exists in dictionary`(isDirty: Boolean) { + parsedBody["CFICode"] = "12345" + encodeTest(MSG_ADDITIONAL_FIELD_DICT, "Unexpected field in message. Field name: CFICode. Field value: 12345.", dirtyMode = isDirty) + } + + @Test + fun `decode with addition field that exists in dictionary (dirty)`() { + parsedBody["CFICode"] = "12345" + decodeTest(MSG_ADDITIONAL_FIELD_DICT, "Unexpected field in message. Field name: CFICode. Field value: 12345.", dirtyMode = true) + } + + @Test + fun `decode with addition field that exists in dictionary (non dirty)`() { + parsedBody["CFICode"] = "12345" + // note: Unknown tag in the message causes the processing of messages to stop and moves on to the next part of + // the message. As a result, required tags remain unread, which leads to the following error. + decodeTest(MSG_ADDITIONAL_FIELD_DICT, "Required tag missing. Tag: 10.", dirtyMode = false) + } + + @Test + fun `encode with addition field that does not exists in dictionary`() { + parsedBody["UNKNOWN_FIELD"] = "test_value" + assertThatThrownBy { codec.encode(MessageGroup(listOf(parsedMessage)), reportingContext) } + .isInstanceOf(IllegalStateException::class.java) + .message() + .startsWith("Field does not exist in dictionary. Field name: UNKNOWN_FIELD. Field value: test_value.") + } + + @Test + fun `decode with addition field that does not exists in dictionary (dirty)`() { + parsedBody["9999"] = "54321" + decodeTest(MSG_ADDITIONAL_FIELD_NO_DICT, "Field does not exist in dictionary. Field tag: 9999. Field value: 54321.", dirtyMode = true) + } + + @Test + fun `decode with addition field that does not exists in dictionary (non dirty)`() { + parsedBody["9999"] = "54321" + // note: Unknown tag in the message causes the processing of messages to stop and moves on to the next part of + // the message. As a result, required tags remain unread, which leads to the following error. + decodeTest(MSG_ADDITIONAL_FIELD_NO_DICT, "Required tag missing. Tag: 10.", dirtyMode = false) + } + + @Test + fun `encode with addition field that contain tag instead of name (dirty)`() { + parsedBody["9999"] = "12345" // field doesn't exist in dictionary + encodeTest(MSG_ADDITIONAL_FIELD_TAG, "Tag instead of field name. Field name: 9999. Field value: 12345.", dirtyMode = true) + } + + @Test + fun `encode with addition field that contain tag instead of name (non dirty)`() { + parsedBody["9999"] = "12345" // field doesn't exist in dictionary + encodeTest(MSG_ADDITIONAL_FIELD_TAG, "Unexpected field in message. Field name: 9999. Field value: 12345.", dirtyMode = false) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `encode with required field removed`(isDirty: Boolean) { + parsedBody.remove("ExecID") + encodeTest(MSG_REQUIRED_FIELD_REMOVED, "Required field missing. Field name: ExecID.", dirtyMode = isDirty) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `decode with required field removed`(isDirty: Boolean) { + parsedBody.remove("ExecID") + decodeTest(MSG_REQUIRED_FIELD_REMOVED, "Required tag missing. Tag: 17.", dirtyMode = isDirty) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `encode with required delimiter field in group removed in first entry`(isDirty: Boolean) { + @Suppress("UNCHECKED_CAST") + ((parsedBody["TradingParty"] as Map)["NoPartyIDs"] as List>)[0].remove("PartyID") + encodeTest(MSG_DELIMITER_FIELD_IN_GROUP_REMOVED_IN_FIRST_ENTRY, "Required field missing. Field name: PartyID.", dirtyMode = isDirty) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `decode with required delimiter field in group removed in first entry`(isDirty: Boolean) { + @Suppress("UNCHECKED_CAST") + ((parsedBody["TradingParty"] as Map)["NoPartyIDs"] as List>)[0].remove("PartyID") + decodeTest(MSG_DELIMITER_FIELD_IN_GROUP_REMOVED_IN_FIRST_ENTRY, "Field PartyIDSource (447) appears before delimiter (448)", dirtyMode = isDirty) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `encode with required delimiter field in group removed in second entry`(isDirty: Boolean) { + @Suppress("UNCHECKED_CAST") + ((parsedBody["TradingParty"] as Map)["NoPartyIDs"] as List>)[1].remove("PartyID") + encodeTest(MSG_DELIMITER_FIELD_IN_GROUP_REMOVED_IN_SECOND_ENTRY, "Required field missing. Field name: PartyID.", dirtyMode = isDirty) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `decode with required delimiter field in group removed in second entry`(isDirty: Boolean) { + @Suppress("UNCHECKED_CAST") + ((parsedBody["TradingParty"] as Map)["NoPartyIDs"] as List>)[1].remove("PartyID") + decodeTest(MSG_DELIMITER_FIELD_IN_GROUP_REMOVED_IN_SECOND_ENTRY, "Field PartyIDSource (447) appears before delimiter (448)", dirtyMode = isDirty) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `encode with wrong enum value`(isDirty: Boolean) { + parsedBody["ExecType"] = 'X' + encodeTest(MSG_WRONG_ENUM, "Invalid value in enum field ExecType. Actual: X.", dirtyMode = isDirty) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `decode with wrong enum value`(isDirty: Boolean) { + parsedBody["ExecType"] = 'X' + decodeTest(MSG_WRONG_ENUM, "Invalid value in enum field ExecType. Actual: X.", dirtyMode = isDirty) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `encode with correct enum value as string`(isDirty: Boolean) { + parsedBody["ExecType"] = "0" + encodeTest(MSG_CORRECT, dirtyMode = isDirty) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `encode with wrong value type`(isDirty: Boolean) { + parsedBody["LeavesQty"] = "Five" // String instead of BigDecimal + encodeTest(MSG_WRONG_TYPE, "Wrong number value in java.math.BigDecimal field 'LeavesQty'. Value: Five.", dirtyMode = isDirty) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `encode with correct BigDecimal value in string`(isDirty: Boolean) { + parsedBody["LeavesQty"] = "500" // String instead of BigDecimal + encodeTest(MSG_CORRECT, dirtyMode = isDirty) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `decode with wrong value type`(isDirty: Boolean) { + parsedBody["LeavesQty"] = "Five" + decodeTest(MSG_WRONG_TYPE, "Wrong number value in java.math.BigDecimal field 'LeavesQty'. Value: Five.", dirtyMode = isDirty) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `encode with empty value`(isDirty: Boolean) { + parsedBody["Account"] = "" + encodeTest(MSG_EMPTY_VAL, "Empty value in the field 'Account'.", dirtyMode = isDirty) + } + + @Test + fun `decode with empty value (dirty)`() { + parsedBody["Account"] = "" + decodeTest(MSG_EMPTY_VAL, "Empty value in the field 'Account'.", dirtyMode = true) + } + + @Test + fun `decode with empty value (non dirty)`() { + parsedBody["Account"] = "" + decodeTest(MSG_EMPTY_VAL, "No valid value at offset: 235", dirtyMode = false) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `encode with non printable characters`(isDirty: Boolean) { + parsedBody["Account"] = "test\taccount" + encodeTest(MSG_NON_PRINTABLE, "Non-printable characters in the field 'Account'. Value: test\taccount", dirtyMode = isDirty) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `decode with non printable characters`(isDirty: Boolean) { + parsedBody["Account"] = "test\taccount" + decodeTest(MSG_NON_PRINTABLE, "Non printable characters in the field 'Account'.", dirtyMode = isDirty) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `encode with calculated required fields removed`(isDirty: Boolean) { + @Suppress("UNCHECKED_CAST") + val header = parsedBody["header"] as MutableMap + header.remove("BeginString") + header.remove("BodyLength") + header.remove("MsgType") + @Suppress("UNCHECKED_CAST") + (parsedBody["trailer"] as MutableMap).remove("CheckSum") + encodeTest(MSG_CORRECT, dirtyMode = isDirty) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `encode with calculated required header fields removed`(isDirty: Boolean) { + @Suppress("UNCHECKED_CAST") + val header = parsedBody["header"] as MutableMap + header.remove("SenderCompID") + header.remove("TargetCompID") + header.remove("MsgSeqNum") + header.remove("SendingTime") + encodeTest(MSG_REQUIRED_HEADER_REMOVED, dirtyMode = isDirty) + } + + @Test + fun `tag appears out of order (dirty)`() { + @Suppress("UNCHECKED_CAST") + val trailer = parsedBody["trailer"] as MutableMap + trailer["LegUnitOfMeasure"] = "500" + decodeTest(MSG_TAG_OUT_OF_ORDER, "Unexpected field in message. Field name: LegUnitOfMeasure. Field value: 500", dirtyMode = true) + } + + @Test + fun `tag appears out of order (non dirty)`() = decodeTest(MSG_TAG_OUT_OF_ORDER, "Tag appears out of order: 999", dirtyMode = false) + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `decode nested components`(isDirty: Boolean) = + decodeTest(MSG_NESTED_REQ_COMPONENTS, expectedMessage = parsedMessageWithNestedComponents, dirtyMode = isDirty) + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `decode with missing req field in req nested component`(isDirty: Boolean) { + @Suppress("UNCHECKED_CAST") + (parsedBodyWithNestedComponents["OuterComponent"] as MutableMap>)["InnerComponent"]?.remove("OrdType") + decodeTest(MSG_NESTED_REQ_COMPONENTS_MISSED_REQ, expectedErrorText = "Required tag missing. Tag: 40.", expectedMessage = parsedMessageWithNestedComponents, dirtyMode = isDirty) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `decode with missing optional field in req nested component`(isDirty: Boolean) { + @Suppress("UNCHECKED_CAST") + (parsedBodyWithNestedComponents["OuterComponent"] as MutableMap>)["InnerComponent"]?.remove("Text") + decodeTest(MSG_NESTED_REQ_COMPONENTS_MISSED_OPTIONAL, expectedMessage = parsedMessageWithNestedComponents, dirtyMode = isDirty) + } + + private fun convertToOptionalComponent(): ParsedMessage { + @Suppress("UNCHECKED_CAST") + (parsedBodyWithNestedComponents["header"] as MutableMap)["MsgType"] = "TEST_2" + val msgBuilder = parsedMessageWithNestedComponents.toBuilder() + msgBuilder.setType("NestedOptionalComponentTestMessage") + return msgBuilder.build() + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `decode nested optional components`(isDirty: Boolean) { + val message = convertToOptionalComponent() + decodeTest(MSG_NESTED_OPT_COMPONENTS, expectedMessage = message, dirtyMode = isDirty) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `decode with missing req field in opt nested component`(isDirty: Boolean) { + val message = convertToOptionalComponent() + @Suppress("UNCHECKED_CAST") + (parsedBodyWithNestedComponents["OuterComponent"] as MutableMap>)["InnerComponent"]?.remove("OrdType") + decodeTest(MSG_NESTED_OPT_COMPONENTS_MISSED_REQ, expectedErrorText = "Required tag missing. Tag: 40.", expectedMessage = message, dirtyMode = isDirty) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `decode with missing all fields in opt nested component`(isDirty: Boolean) { + val message = convertToOptionalComponent() + @Suppress("UNCHECKED_CAST") + (parsedBodyWithNestedComponents["OuterComponent"] as MutableMap>).remove("InnerComponent") + decodeTest(MSG_NESTED_OPT_COMPONENTS_MISSED_ALL_FIELDS, expectedErrorText = "Required tag missing. Tag: 40.", expectedMessage = message, dirtyMode = isDirty) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `decode with missing all fields in inner and outer nested components`(isDirty: Boolean) { + val message = convertToOptionalComponent() + parsedBodyWithNestedComponents.remove("OuterComponent") + decodeTest(MSG_NESTED_OPT_COMPONENTS_MISSED_ALL_FIELDS_INNER_AND_OUTER, expectedMessage = message, dirtyMode = isDirty) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `decode with missing req fields in both inner and outer components`(isDirty: Boolean) { + val message = convertToOptionalComponent() + @Suppress("UNCHECKED_CAST") + (parsedBodyWithNestedComponents["OuterComponent"] as MutableMap>).remove("LeavesQty") + @Suppress("UNCHECKED_CAST") + (parsedBodyWithNestedComponents["OuterComponent"] as MutableMap>)["InnerComponent"]!!.remove("OrdType") + decodeTest( + MSG_NESTED_OPT_COMPONENTS_MISSED_ALL_OUTER_FIELDS_AND_REQ_INNER_FIELD, + expectedErrorText = "Required tag missing. Tag: 40.", + expectedSecondErrorText = "Required tag missing. Tag: 151.", + expectedMessage = message, + dirtyMode = isDirty + ) + } + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `encode nested groups`(isDirty: Boolean) = encodeTest(MSG_NESTED_GROUPS, dirtyMode = isDirty, parsedMessage = parsedMessageWithNestedGroups) + + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun `decode nested groups`(isDirty: Boolean) = decodeTest(MSG_NESTED_GROUPS, expectedMessage = parsedMessageWithNestedGroups, dirtyMode = isDirty) + + private fun encodeTest( + expectedRawMessage: String, + expectedError: String? = null, + dirtyMode: Boolean, + encodeFromStringValues: Boolean = false, + parsedMessage: ParsedMessage = this.parsedMessage + ) { + if (dirtyMode) { + encodeTestDirty(expectedRawMessage, expectedError, parsedMessage, encodeFromStringValues) + } else { + encodeTestNonDirty(expectedRawMessage, expectedError, parsedMessage, encodeFromStringValues) + } + } + + private fun encodeTestDirty( + expectedRawMessage: String, + expectedWarning: String? = null, + parsedMessage: ParsedMessage, + encodeFromStringValues: Boolean = false + ) { + val parsedBody = parsedMessage.body as MutableMap + + if (encodeFromStringValues) { + @Suppress("UNCHECKED_CAST") + val stringBody = convertValuesToString(parsedBody) as Map + parsedBody.putAll(stringBody) + } + + val encoded = codec.encode(MessageGroup(listOf(parsedMessage)), reportingContext) + val body = encoded.messages.single().body as CompositeByteBuf + val fixMsg = body.toString(StandardCharsets.US_ASCII) + assertThat(fixMsg).isEqualTo(expectedRawMessage) + if (expectedWarning == null) { + assertThat(reportingContext.warnings).isEmpty() + } else { + assertThat(reportingContext.warnings.single()).startsWith(DIRTY_MODE_WARNING_PREFIX + expectedWarning) + } + } + + private fun encodeTestNonDirty( + expectedRawMessage: String, + expectedError: String? = null, + parsedMessage: ParsedMessage, + encodeFromStringValues: Boolean = false + ) { + val parsedBody = parsedMessage.body as MutableMap + + if (encodeFromStringValues) { + @Suppress("UNCHECKED_CAST") + val stringBody = convertValuesToString(parsedBody) as Map + parsedBody.putAll(stringBody) + } + + val parsed = parsedMessage.toBuilder().apply { metadataBuilder().remove("encode-mode").build() }.build() + + if (expectedError != null) { + assertThatThrownBy { + codec.encode(MessageGroup(listOf(parsed)), reportingContext) + println() + }.isInstanceOf(IllegalStateException::class.java).message() + .startsWith(expectedError) + } else { + val encoded = codec.encode(MessageGroup(listOf(parsed)), reportingContext) + + val body = encoded.messages.single().body as CompositeByteBuf + val fixMsg = body.toString(StandardCharsets.US_ASCII) + assertThat(fixMsg).isEqualTo(expectedRawMessage) + } + } + + private fun decodeTest( + rawMessageString: String, + expectedErrorText: String? = null, + expectedSecondErrorText: String? = null, + expectedMessage: ParsedMessage = parsedMessage, + dirtyMode: Boolean, + decodeToStringValues: Boolean = false + ) { + if (dirtyMode) { + decodeTestDirty( + rawMessageString, + expectedErrorText, + expectedSecondErrorText, + expectedMessage, + decodeToStringValues + ) + } else { + decodeTestNonDirty( + rawMessageString, + expectedErrorText, + expectedMessage, + decodeToStringValues + ) + } + } + + private fun decodeTestDirty( + rawMessageString: String, + expectedErrorText: String? = null, + expectedSecondErrorText: String? = null, + expectedMessage: ParsedMessage = parsedMessage, + decodeToStringValues: Boolean = false + ) { + val expectedBody = expectedMessage.body + val rawMessage = RawMessage( + id = parsedMessage.id, + eventId = parsedMessage.eventId, + metadata = mapOf("encode-mode" to "dirty"), + body = Unpooled.wrappedBuffer(rawMessageString.toByteArray(Charsets.US_ASCII)) + ) + + val codec = if (decodeToStringValues) FixNgCodec(dictionary, FixNgCodecSettings(dictionary = "")) else this.codec + val decodedGroup = codec.decode(MessageGroup(listOf(rawMessage)), reportingContext) + val parsedMessage = decodedGroup.messages.single() as ParsedMessage + + // we don't validate `CheckSum` and `BodyLength` in incorrect messages + val fieldsToIgnore = if (expectedErrorText == null) emptyArray() else arrayOf("trailer.CheckSum", "header.BodyLength") + val expected = if (decodeToStringValues) convertValuesToString(expectedBody) else expectedBody + + assertThat(parsedMessage.body) + .usingRecursiveComparison() + .ignoringFields(*fieldsToIgnore) + .isEqualTo(expected) + + if (expectedErrorText == null) { + assertThat(reportingContext.warnings).isEmpty() + } else { + if (expectedSecondErrorText == null) { + assertThat(reportingContext.warnings.single()).startsWith(DIRTY_MODE_WARNING_PREFIX + expectedErrorText) + } else { + assertThat(reportingContext.warnings).size().isEqualTo(2) + assertThat(reportingContext.warnings[0]).startsWith(DIRTY_MODE_WARNING_PREFIX + expectedErrorText) + assertThat(reportingContext.warnings[1]).startsWith(DIRTY_MODE_WARNING_PREFIX + expectedSecondErrorText) + } + } + } + + private fun decodeTestNonDirty( + rawMessageString: String, + expectedErrorText: String? = null, + expectedMessage: ParsedMessage = parsedMessage, + decodeToStringValues: Boolean = false + ) { + val expectedBody = expectedMessage.body + val rawMessage = RawMessage( + id = parsedMessage.id, + eventId = parsedMessage.eventId, + metadata = emptyMap(), + body = Unpooled.wrappedBuffer(rawMessageString.toByteArray(Charsets.US_ASCII)) + ) + + val codec = if (decodeToStringValues) FixNgCodec(dictionary, FixNgCodecSettings(dictionary = "")) else this.codec + if (expectedErrorText != null) { + assertThatThrownBy { + codec.decode(MessageGroup(listOf(rawMessage)), reportingContext) + }.isInstanceOf(IllegalStateException::class.java).message().startsWith(expectedErrorText) + } else { + val decodedGroup = codec.decode(MessageGroup(listOf(rawMessage)), reportingContext) + val parsedMessage = decodedGroup.messages.single() as ParsedMessage + val expected = if (decodeToStringValues) convertValuesToString(expectedBody) else expectedBody + assertThat(parsedMessage.body) + .usingRecursiveComparison() + .isEqualTo(expected) + } + } + + private fun convertValuesToString(value: Any?): Any = when (value) { + is Map<*, *> -> value.mapValues { convertValuesToString(it.value) } + is List<*> -> value.map(::convertValuesToString) + else -> value.toString() + } + + private val parsedMessage = ParsedMessage( + MessageId("test_alias", Direction.OUTGOING, 0L, Instant.now(), emptyList()), + EventId("test_id", "test_book", "test_scope", Instant.now()), + "ExecutionReport", + mutableMapOf("encode-mode" to "dirty"), + PROTOCOL, + mutableMapOf( + "header" to mutableMapOf( + "MsgSeqNum" to 10947, + "SenderCompID" to "SENDER", + "SendingTime" to LocalDateTime.parse("2023-04-19T10:36:07.415088"), + "TargetCompID" to "RECEIVER", + "BeginString" to "FIXT.1.1", + "BodyLength" to 295, + "MsgType" to "8" + ), + "ExecID" to "495504662", + "ClOrdID" to "zSuNbrBIZyVljs", + "OrigClOrdID" to "zSuNbrBIZyVljs", + "OrderID" to "49415882", + "ExecType" to '0', + "OrdStatus" to '0', + "LeavesQty" to BigDecimal(500), + "CumQty" to BigDecimal(500), + "SecurityID" to "NWDR", + "SecurityIDSource" to "8", + "TradingParty" to mutableMapOf( + "NoPartyIDs" to mutableListOf( + mutableMapOf( + "PartyID" to "NGALL1FX01", + "PartyIDSource" to 'D', + "PartyRole" to 76 + ), + mutableMapOf( + "PartyID" to "0", + "PartyIDSource" to 'P', + "PartyRole" to 3 + ) + ) + ), + "Account" to "test", + "OrdType" to 'A', + "TimeInForce" to '0', + "Side" to 'B', + "Symbol" to "ABC", + "OrderQty" to BigDecimal(500), + "Price" to BigDecimal(1000), + "Unknown" to "500", + "TransactTime" to LocalDateTime.parse("2018-02-05T10:38:08.000008"), + "trailer" to mutableMapOf( + "CheckSum" to "191" + ) + ) + ) + + private val parsedBody: MutableMap = parsedMessage.body as MutableMap + + private val expectedMessageWithoutBody = ParsedMessage( + MessageId("test_alias", Direction.OUTGOING, 0L, Instant.now(), emptyList()), + EventId("test_id", "test_book", "test_scope", Instant.now()), + "ExecutionReport", + mutableMapOf("encode-mode" to "dirty"), + PROTOCOL, + mutableMapOf( + "header" to mapOf( + "MsgSeqNum" to 125, + "SenderCompID" to "MZHOT0", + "SendingTime" to LocalDateTime.parse("2024-08-01T08:03:01.229"), + "TargetCompID" to "INET", + "BeginString" to "FIX.4.2", + "BodyLength" to 55, + "MsgType" to "0" + ), + "trailer" to mapOf( + "CheckSum" to "039" + ) + ) + ) + + private val parsedMessageWithNestedComponents = ParsedMessage( + MessageId("test_alias", Direction.OUTGOING, 0L, Instant.now(), emptyList()), + EventId("test_id", "test_book", "test_scope", Instant.now()), + "NestedRequiredComponentTestMessage", + mutableMapOf("encode-mode" to "dirty"), + PROTOCOL, + mutableMapOf( + "header" to mutableMapOf( + "BeginString" to "FIXT.1.1", + "BodyLength" to 59, + "MsgType" to "TEST_1", + "MsgSeqNum" to 125, + "TargetCompID" to "INET", + "SenderCompID" to "MZHOT0" + ), + "OuterComponent" to mutableMapOf( + "LeavesQty" to BigDecimal(1234), // tag 151 + "InnerComponent" to mutableMapOf( + "Text" to "text_1", // tag 58 + "OrdType" to '1' // 40 + ) + ), + "trailer" to mapOf( + "CheckSum" to "191" + ) + ) + ) + private val parsedBodyWithNestedComponents: MutableMap = parsedMessageWithNestedComponents.body as MutableMap + + private val parsedMessageWithNestedGroups = ParsedMessage( + MessageId("test_alias", Direction.OUTGOING, 0L, Instant.now(), emptyList()), + EventId("test_id", "test_book", "test_scope", Instant.now()), + "NestedGroupsTestMessage", + mutableMapOf("encode-mode" to "dirty"), + PROTOCOL, + mutableMapOf( + "header" to mutableMapOf( + "BeginString" to "FIXT.1.1", + "BodyLength" to 88, + "MsgType" to "TEST_3", + "MsgSeqNum" to 125, + "TargetCompID" to "INET", + "SenderCompID" to "MZHOT0" + ), + "OuterGroup" to mutableMapOf( + "NoOrders" to mutableListOf( + mutableMapOf( + "NestedGroups" to mutableMapOf( + "NoBidDescriptors" to mutableListOf( + mutableMapOf("BidDescriptorType" to 1), + mutableMapOf("BidDescriptorType" to 2), + mutableMapOf("BidDescriptorType" to 3) + ) + ) + ), + mutableMapOf( + "NestedGroups" to mutableMapOf( + "NoBidDescriptors" to mutableListOf( + mutableMapOf("BidDescriptorType" to 3), + mutableMapOf("BidDescriptorType" to 2), + mutableMapOf("BidDescriptorType" to 1) + ) + ) + ) + ) + ), + "trailer" to mapOf( + "CheckSum" to "211" + ) + ) + ) + + companion object { + private const val DIRTY_MODE_WARNING_PREFIX = "Dirty mode WARNING: " + + private const val MSG_CORRECT = "8=FIXT.1.1\u00019=295\u000135=8\u000149=SENDER\u000156=RECEIVER\u000134=10947\u000152=20230419-10:36:07.415088\u000117=495504662\u000111=zSuNbrBIZyVljs\u000141=zSuNbrBIZyVljs\u000137=49415882\u0001150=0\u000139=0\u0001151=500\u000114=500\u000148=NWDR\u000122=8\u0001453=2\u0001448=NGALL1FX01\u0001447=D\u0001452=76\u0001448=0\u0001447=P\u0001452=3\u00011=test\u000140=A\u000159=0\u000154=B\u000155=ABC\u000138=500\u000144=1000\u000147=500\u000160=20180205-10:38:08.000008\u000110=191\u0001" + private const val MSG_CORRECT_WITHOUT_BODY = "8=FIX.4.2\u00019=55\u000135=0\u000134=125\u000149=MZHOT0\u000152=20240801-08:03:01.229\u000156=INET\u000110=039\u0001" + private const val MSG_ADDITIONAL_FIELD_DICT = "8=FIXT.1.1\u00019=305\u000135=8\u000149=SENDER\u000156=RECEIVER\u000134=10947\u000152=20230419-10:36:07.415088\u000117=495504662\u000111=zSuNbrBIZyVljs\u000141=zSuNbrBIZyVljs\u000137=49415882\u0001150=0\u000139=0\u0001151=500\u000114=500\u000148=NWDR\u000122=8\u0001453=2\u0001448=NGALL1FX01\u0001447=D\u0001452=76\u0001448=0\u0001447=P\u0001452=3\u00011=test\u000140=A\u000159=0\u000154=B\u000155=ABC\u000138=500\u000144=1000\u000147=500\u000160=20180205-10:38:08.000008\u0001461=12345\u000110=143\u0001" + private const val MSG_ADDITIONAL_FIELD_NO_DICT = "8=FIXT.1.1\u00019=305\u000135=8\u000149=SENDER\u000156=RECEIVER\u000134=10947\u000152=20230419-10:36:07.415088\u000117=495504662\u000111=zSuNbrBIZyVljs\u000141=zSuNbrBIZyVljs\u000137=49415882\u0001150=0\u000139=0\u0001151=500\u000114=500\u000148=NWDR\u000122=8\u0001453=2\u0001448=NGALL1FX01\u0001447=D\u0001452=76\u0001448=0\u0001447=P\u0001452=3\u00011=test\u000140=A\u000159=0\u000154=B\u000155=ABC\u000138=500\u000144=1000\u000147=500\u000160=20180205-10:38:08.000008\u00019999=54321\u000110=097\u0001" + private const val MSG_ADDITIONAL_FIELD_TAG = "8=FIXT.1.1\u00019=306\u000135=8\u000149=SENDER\u000156=RECEIVER\u000134=10947\u000152=20230419-10:36:07.415088\u000117=495504662\u000111=zSuNbrBIZyVljs\u000141=zSuNbrBIZyVljs\u000137=49415882\u0001150=0\u000139=0\u0001151=500\u000114=500\u000148=NWDR\u000122=8\u0001453=2\u0001448=NGALL1FX01\u0001447=D\u0001452=76\u0001448=0\u0001447=P\u0001452=3\u00011=test\u000140=A\u000159=0\u000154=B\u000155=ABC\u000138=500\u000144=1000\u000147=500\u000160=20180205-10:38:08.000008\u00019999=12345\u000110=217\u0001" + private const val MSG_REQUIRED_FIELD_REMOVED = "8=FIXT.1.1\u00019=282\u000135=8\u000149=SENDER\u000156=RECEIVER\u000134=10947\u000152=20230419-10:36:07.415088\u000111=zSuNbrBIZyVljs\u000141=zSuNbrBIZyVljs\u000137=49415882\u0001150=0\u000139=0\u0001151=500\u000114=500\u000148=NWDR\u000122=8\u0001453=2\u0001448=NGALL1FX01\u0001447=D\u0001452=76\u0001448=0\u0001447=P\u0001452=3\u00011=test\u000140=A\u000159=0\u000154=B\u000155=ABC\u000138=500\u000144=1000\u000147=500\u000160=20180205-10:38:08.000008\u000110=060\u0001" + private const val MSG_DELIMITER_FIELD_IN_GROUP_REMOVED_IN_FIRST_ENTRY = "8=FIXT.1.1\u00019=280\u000135=8\u000149=SENDER\u000156=RECEIVER\u000134=10947\u000152=20230419-10:36:07.415088\u000117=495504662\u000111=zSuNbrBIZyVljs\u000141=zSuNbrBIZyVljs\u000137=49415882\u0001150=0\u000139=0\u0001151=500\u000114=500\u000148=NWDR\u000122=8\u0001453=2\u0001447=D\u0001452=76\u0001448=0\u0001447=P\u0001452=3\u00011=test\u000140=A\u000159=0\u000154=B\u000155=ABC\u000138=500\u000144=1000\u000147=500\u000160=20180205-10:38:08.000008\u000110=061\u0001" + private const val MSG_DELIMITER_FIELD_IN_GROUP_REMOVED_IN_SECOND_ENTRY = "8=FIXT.1.1\u00019=289\u000135=8\u000149=SENDER\u000156=RECEIVER\u000134=10947\u000152=20230419-10:36:07.415088\u000117=495504662\u000111=zSuNbrBIZyVljs\u000141=zSuNbrBIZyVljs\u000137=49415882\u0001150=0\u000139=0\u0001151=500\u000114=500\u000148=NWDR\u000122=8\u0001453=2\u0001448=NGALL1FX01\u0001447=D\u0001452=76\u0001447=P\u0001452=3\u00011=test\u000140=A\u000159=0\u000154=B\u000155=ABC\u000138=500\u000144=1000\u000147=500\u000160=20180205-10:38:08.000008\u000110=180\u0001" + private const val MSG_WRONG_ENUM = "8=FIXT.1.1\u00019=295\u000135=8\u000149=SENDER\u000156=RECEIVER\u000134=10947\u000152=20230419-10:36:07.415088\u000117=495504662\u000111=zSuNbrBIZyVljs\u000141=zSuNbrBIZyVljs\u000137=49415882\u0001150=X\u000139=0\u0001151=500\u000114=500\u000148=NWDR\u000122=8\u0001453=2\u0001448=NGALL1FX01\u0001447=D\u0001452=76\u0001448=0\u0001447=P\u0001452=3\u00011=test\u000140=A\u000159=0\u000154=B\u000155=ABC\u000138=500\u000144=1000\u000147=500\u000160=20180205-10:38:08.000008\u000110=231\u0001" + private const val MSG_WRONG_TYPE = "8=FIXT.1.1\u00019=296\u000135=8\u000149=SENDER\u000156=RECEIVER\u000134=10947\u000152=20230419-10:36:07.415088\u000117=495504662\u000111=zSuNbrBIZyVljs\u000141=zSuNbrBIZyVljs\u000137=49415882\u0001150=0\u000139=0\u0001151=Five\u000114=500\u000148=NWDR\u000122=8\u0001453=2\u0001448=NGALL1FX01\u0001447=D\u0001452=76\u0001448=0\u0001447=P\u0001452=3\u00011=test\u000140=A\u000159=0\u000154=B\u000155=ABC\u000138=500\u000144=1000\u000147=500\u000160=20180205-10:38:08.000008\u000110=181\u0001" + private const val MSG_EMPTY_VAL = "8=FIXT.1.1\u00019=291\u000135=8\u000149=SENDER\u000156=RECEIVER\u000134=10947\u000152=20230419-10:36:07.415088\u000117=495504662\u000111=zSuNbrBIZyVljs\u000141=zSuNbrBIZyVljs\u000137=49415882\u0001150=0\u000139=0\u0001151=500\u000114=500\u000148=NWDR\u000122=8\u0001453=2\u0001448=NGALL1FX01\u0001447=D\u0001452=76\u0001448=0\u0001447=P\u0001452=3\u00011=\u000140=A\u000159=0\u000154=B\u000155=ABC\u000138=500\u000144=1000\u000147=500\u000160=20180205-10:38:08.000008\u000110=251\u0001" + private const val MSG_NON_PRINTABLE = "8=FIXT.1.1\u00019=303\u000135=8\u000149=SENDER\u000156=RECEIVER\u000134=10947\u000152=20230419-10:36:07.415088\u000117=495504662\u000111=zSuNbrBIZyVljs\u000141=zSuNbrBIZyVljs\u000137=49415882\u0001150=0\u000139=0\u0001151=500\u000114=500\u000148=NWDR\u000122=8\u0001453=2\u0001448=NGALL1FX01\u0001447=D\u0001452=76\u0001448=0\u0001447=P\u0001452=3\u00011=test\taccount\u000140=A\u000159=0\u000154=B\u000155=ABC\u000138=500\u000144=1000\u000147=500\u000160=20180205-10:38:08.000008\u000110=171\u0001" + private const val MSG_REQUIRED_HEADER_REMOVED = "8=FIXT.1.1\u00019=236\u000135=8\u000117=495504662\u000111=zSuNbrBIZyVljs\u000141=zSuNbrBIZyVljs\u000137=49415882\u0001150=0\u000139=0\u0001151=500\u000114=500\u000148=NWDR\u000122=8\u0001453=2\u0001448=NGALL1FX01\u0001447=D\u0001452=76\u0001448=0\u0001447=P\u0001452=3\u00011=test\u000140=A\u000159=0\u000154=B\u000155=ABC\u000138=500\u000144=1000\u000147=500\u000160=20180205-10:38:08.000008\u000110=050\u0001" + private const val MSG_TAG_OUT_OF_ORDER = "8=FIXT.1.1\u00019=295\u000135=8\u000149=SENDER\u000156=RECEIVER\u000134=10947\u000152=20230419-10:36:07.415088\u000117=495504662\u000111=zSuNbrBIZyVljs\u000141=zSuNbrBIZyVljs\u000137=49415882\u0001150=0\u000139=0\u0001151=500\u000114=500\u000148=NWDR\u000122=8\u0001453=2\u0001448=NGALL1FX01\u0001447=D\u0001452=76\u0001448=0\u0001447=P\u0001452=3\u00011=test\u000140=A\u000159=0\u000154=B\u000155=ABC\u000138=500\u000144=1000\u000147=500\u000160=20180205-10:38:08.000008\u000110=000\u0001999=500\u0001" + + private const val MSG_NESTED_REQ_COMPONENTS = "8=FIXT.1.19=5935=TEST_149=MZHOT056=INET34=12558=text_140=1151=123410=191" + private const val MSG_NESTED_REQ_COMPONENTS_MISSED_REQ = "8=FIXT.1.19=5935=TEST_149=MZHOT056=INET34=12558=text_1151=123410=191" + private const val MSG_NESTED_REQ_COMPONENTS_MISSED_OPTIONAL = "8=FIXT.1.19=5935=TEST_149=MZHOT056=INET34=12540=1151=123410=191" + + private const val MSG_NESTED_OPT_COMPONENTS = "8=FIXT.1.19=5935=TEST_249=MZHOT056=INET34=12558=text_140=1151=123410=191" + private const val MSG_NESTED_OPT_COMPONENTS_MISSED_REQ = "8=FIXT.1.19=5935=TEST_249=MZHOT056=INET34=12558=text_1151=123410=191" + private const val MSG_NESTED_OPT_COMPONENTS_MISSED_ALL_FIELDS = "8=FIXT.1.19=5935=TEST_249=MZHOT056=INET34=125151=123410=191" + private const val MSG_NESTED_OPT_COMPONENTS_MISSED_ALL_FIELDS_INNER_AND_OUTER = "8=FIXT.1.19=5935=TEST_249=MZHOT056=INET34=12510=191" + private const val MSG_NESTED_OPT_COMPONENTS_MISSED_ALL_OUTER_FIELDS_AND_REQ_INNER_FIELD = "8=FIXT.1.19=5935=TEST_249=MZHOT056=INET34=12558=text_110=191" + + private const val MSG_NESTED_GROUPS = "8=FIXT.1.19=8835=TEST_349=MZHOT056=INET34=12573=2398=3399=1399=2399=3398=3399=3399=2399=110=211" + } +} \ No newline at end of file diff --git a/src/test/resources/dictionary.xml b/src/test/resources/dictionary.xml new file mode 100644 index 0000000..f682a15 --- /dev/null +++ b/src/test/resources/dictionary.xml @@ -0,0 +1,7510 @@ + + + + + + + 1409 + INT + 0 + 2 + 3 + 4 + 6 + 7 + 8 + 100 + 101 + 102 + + + 1 + STRING + + + 2 + STRING + + + 3 + STRING + + + 4 + CHAR + B + S + X + T + + + 5 + STRING + N + C + R + + + 6 + PRICE + + + 7 + SEQNUM + + + 8 + STRING + + + 9 + LENGTH + + + 10 + STRING + + + 11 + STRING + + + 12 + AMT + + + 13 + CHAR + 1 + 2 + 3 + 4 + 5 + 6 + + + 14 + QTY + + + 15 + CURRENCY + + + 16 + SEQNUM + + + 17 + STRING + + + 18 + MULTIPLECHARVALUE + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + A + B + C + D + E + F + G + H + I + J + K + L + M + N + O + P + Q + R + S + T + U + V + W + X + Y + Z + a + b + c + d + e + f + g + h + i + j + k + + + 19 + STRING + + + 20 + CHAR + 0 + 1 + 2 + 3 + + + 21 + CHAR + 1 + 2 + 3 + + + 22 + STRING + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + A + B + C + D + E + F + G + H + I + J + K + L + + + 23 + STRING + + + 25 + CHAR + L + M + H + + + 26 + STRING + + + 27 + STRING + 0 + S + M + L + U + + + 28 + CHAR + N + C + R + + + 29 + CHAR + 1 + 2 + 3 + 4 + + + 30 + EXCHANGE + + + 31 + PRICE + + + 32 + QTY + + + 33 + NUMINGROUP + + + 34 + SEQNUM + + + 35 + STRING + 0 + 1 + 2 + 3 + 4 + 5 + 8 + 9 + A + D + E + F + G + S + Z + b + e + f + j + k + q + r + s + u + AD + AE + AF + AI + AQ + AR + BW + BX + + + 36 + SEQNUM + + + 37 + STRING + + + 38 + QTY + + + 39 + CHAR + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + A + B + C + D + E + + + 40 + CHAR + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + A + B + C + D + E + F + G + H + I + J + K + L + M + P + Q + + + 41 + STRING + + + 42 + UTCTIMESTAMP + + + 43 + BOOLEAN + + + 44 + PRICE + + + 45 + SEQNUM + + + 47 + STRING + + + 48 + STRING + + + 49 + STRING + + + 50 + STRING + + + 52 + UTCTIMESTAMP + + + 53 + QTY + + + 54 + CHAR + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + A + B + C + D + E + F + G + + + 55 + STRING + + + 56 + STRING + + + 57 + STRING + + + 58 + STRING + + + 59 + CHAR + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + C + + + 60 + UTCTIMESTAMP + + + 61 + CHAR + 0 + 1 + 2 + + + 62 + UTCTIMESTAMP + + + 63 + STRING + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + C + B + + + 64 + LOCALMKTDATE + + + 65 + STRING + WI + CD + + + 66 + STRING + + + 67 + INT + + + 68 + INT + + + 69 + STRING + + + 70 + STRING + + + 71 + CHAR + 0 + 1 + 2 + 3 + 4 + 5 + 6 + + + 72 + STRING + + + 73 + NUMINGROUP + + + 74 + INT + + + 75 + LOCALMKTDATE + + + 76 + STRING + + + 77 + CHAR + O + C + R + F + + + 78 + NUMINGROUP + + + 79 + STRING + + + 80 + QTY + + + 81 + CHAR + 0 + 1 + 2 + 3 + 4 + 5 + 6 + + + 82 + INT + + + 83 + INT + + + 84 + QTY + + + 85 + NUMINGROUP + + + 86 + STRING + + + 87 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + + + 88 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + + + 89 + DATA + + + 90 + LENGTH + + + 91 + DATA + + + 92 + STRING + + + 93 + LENGTH + + + 94 + CHAR + 0 + 1 + 2 + + + 95 + LENGTH + + + 96 + DATA + + + 97 + BOOLEAN + + + 98 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + + + 99 + PRICE + + + 100 + EXCHANGE + + + 102 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 18 + 99 + 113100 + 113101 + + + 103 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 18 + 99 + 10000 + 10001 + 10003 + 10004 + 10005 + 10006 + + + 104 + CHAR + A + B + C + D + I + L + M + O + P + Q + R + S + T + V + W + X + Y + Z + + + 105 + STRING + + + 106 + STRING + + + 107 + STRING + + + 108 + INT + + + 109 + STRING + + + 110 + QTY + + + 111 + QTY + + + 112 + STRING + + + 113 + BOOLEAN + + + 114 + BOOLEAN + + + 115 + STRING + + + 116 + STRING + + + 117 + STRING + + + 118 + AMT + + + 119 + AMT + + + 120 + CURRENCY + + + 121 + BOOLEAN + + + 122 + UTCTIMESTAMP + + + 123 + BOOLEAN + + + 124 + NUMINGROUP + + + 125 + CHAR + P + F + + + 126 + UTCTIMESTAMP + + + 127 + CHAR + A + B + C + D + E + F + Z + + + 128 + STRING + + + 129 + STRING + + + 130 + BOOLEAN + + + 131 + STRING + + + 132 + PRICE + + + 133 + PRICE + + + 134 + QTY + + + 135 + QTY + + + 136 + NUMINGROUP + + + 137 + AMT + + + 138 + CURRENCY + + + 139 + STRING + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + + 140 + PRICE + + + 141 + BOOLEAN + + + 142 + STRING + + + 143 + STRING + + + 144 + STRING + + + 145 + STRING + + + 146 + NUMINGROUP + + + 147 + STRING + + + 148 + STRING + + + 149 + STRING + + + 150 + CHAR + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + A + B + C + D + E + F + G + H + I + J + K + L + + + 151 + QTY + + + 152 + QTY + + + 153 + PRICE + + + 154 + AMT + + + 155 + FLOAT + + + 156 + CHAR + M + D + + + 157 + INT + + + 158 + PERCENTAGE + + + 159 + AMT + + + 160 + CHAR + 0 + 1 + 2 + 3 + 4 + 5 + + + 161 + STRING + + + 162 + STRING + + + 163 + CHAR + N + C + R + T + + + 164 + STRING + + + 165 + CHAR + 1 + 2 + 3 + + + 166 + STRING + CED + DTC + EUR + FED + ISO + PNY + PTC + + + 167 + STRING + ? + ABS + AMENDED + AN + BA + BN + BOX + BRADY + BRIDGE + BUYSELL + CB + CD + CL + CMBS + CMO + COFO + COFP + CORP + CP + CPP + CS + DEFLTED + DINP + DN + DUAL + EUCD + EUCORP + EUCP + EUSOV + EUSUPRA + FAC + FADN + FOR + FORWARD + FUT + GO + IET + LOFC + LQN + MATURED + MBS + MF + MIO + MLEG + MPO + MPP + MPT + MT + MTN + NONE + ONITE + OPT + PEF + PFAND + PN + PS + PZFJ + RAN + REPLACD + REPO + RETIRED + REV + RVLV + RVLVTRM + SECLOAN + SECPLEDGE + SPCLA + SPCLO + SPCLT + STN + STRUCT + SUPRA + SWING + TAN + TAXA + TBA + TBILL + TBOND + TCAL + TD + TECP + TERM + TINT + TIPS + TNOTE + TPRN + TRAN + UST + USTB + VRDN + WAR + WITHDRN + WLD + XCN + XLINKD + YANK + YCD + OOP + OOF + CASH + + + 168 + UTCTIMESTAMP + + + 169 + INT + 0 + 1 + 2 + 3 + 4 + + + 170 + STRING + + + 171 + STRING + + + 172 + INT + 0 + 1 + 2 + 3 + + + 173 + STRING + + + 174 + STRING + + + 175 + STRING + + + 176 + STRING + + + 177 + STRING + + + 178 + STRING + + + 179 + STRING + + + 180 + STRING + + + 181 + STRING + + + 182 + STRING + + + 183 + STRING + + + 184 + STRING + + + 185 + STRING + + + 186 + STRING + + + 187 + STRING + + + 188 + PRICE + + + 189 + PRICEOFFSET + + + 190 + PRICE + + + 191 + PRICEOFFSET + + + 192 + QTY + + + 193 + LOCALMKTDATE + + + 194 + PRICE + + + 195 + PRICEOFFSET + + + 196 + STRING + + + 197 + INT + 0 + 1 + + + 198 + STRING + + + 199 + NUMINGROUP + + + 200 + MONTH-YEAR + + + 201 + INT + 0 + 1 + + + 202 + PRICE + + + 203 + INT + 0 + 1 + + + 204 + INT + 0 + 1 + + + 205 + DAY-OF-MONTH + + + 206 + CHAR + + + 207 + EXCHANGE + + + 208 + BOOLEAN + + + 209 + INT + 1 + 2 + 3 + + + 210 + QTY + + + 211 + FLOAT + + + 212 + LENGTH + + + 213 + DATA + + + 214 + STRING + + + 215 + NUMINGROUP + + + 216 + INT + 1 + 2 + 3 + 4 + + + 217 + STRING + + + 218 + PRICEOFFSET + + + 219 + CHAR + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + + + 220 + CURRENCY + + + 221 + STRING + EONIA + EUREPO + Euribor + FutureSWAP + LIBID + LIBOR + MuniAAA + OTHER + Pfandbriefe + SONIA + SWAP + Treasury + + + 222 + STRING + + + 223 + PERCENTAGE + + + 224 + LOCALMKTDATE + + + 225 + LOCALMKTDATE + + + 226 + INT + + + 227 + PERCENTAGE + + + 228 + FLOAT + + + 229 + LOCALMKTDATE + + + 230 + LOCALMKTDATE + + + 231 + FLOAT + + + 232 + NUMINGROUP + + + 233 + STRING + ABS + AMT + AUTOREINV + BANKQUAL + BGNCON + COUPON + CPP + CPR + CPY + CURRENCY + CUSTOMDATE + GEOG + HAIRCUT + HEP + INSURED + ISSUE + ISSUER + ISSUESIZE + LOOKBACK + LOT + LOTVAR + MAT + MATURITY + MAXSUBS + MHP + MINDNOM + MININCR + MINQTY + MPR + PAYFREQ + PIECES + PMAX + PPC + PPL + PPM + PPT + PRICE + PRICEFREQ + PROD + PROTECT + PSA + PURPOSE + PXSOURCE + RATING + REDEMPTION + RESTRICTED + SECTOR + SECTYPE + SMM + STRUCT + SUBSFREQ + SUBSLEFT + TEXT + TRDVAR + WAC + WAL + WALA + WAM + WHOLE + YIELD + + + 234 + STRING + CD + XD + CC + XC + CB + XB + CR + XR + CP + XP + CS + SP + TR + GD + + + 235 + STRING + AFTERTAX + ANNUAL + ATISSUE + AVGMATURITY + BOOK + CALL + CHANGE + CLOSE + COMPOUND + CURRENT + GOVTEQUIV + GROSS + INFLATION + INVERSEFLOATER + LASTCLOSE + LASTMONTH + LASTQUARTER + LASTYEAR + LONGAVGLIFE + MARK + MATURITY + NEXTREFUND + OPENAVG + PREVCLOSE + PROCEEDS + PUT + SEMIANNUAL + SHORTAVGLIFE + SIMPLE + TAXEQUIV + TENDER + TRUE + VALUE1_32 + WORST + + + 236 + PERCENTAGE + + + 237 + AMT + + + 238 + AMT + + + 239 + STRING + + + 240 + LOCALMKTDATE + + + 241 + LOCALMKTDATE + + + 242 + LOCALMKTDATE + + + 243 + STRING + + + 244 + INT + + + 245 + PERCENTAGE + + + 246 + FLOAT + + + 247 + LOCALMKTDATE + + + 248 + LOCALMKTDATE + + + 249 + LOCALMKTDATE + + + 250 + STRING + + + 251 + INT + + + 252 + PERCENTAGE + + + 253 + FLOAT + + + 254 + LOCALMKTDATE + + + 255 + STRING + + + 256 + STRING + + + 257 + STRING + + + 258 + BOOLEAN + + + 259 + LOCALMKTDATE + + + 260 + PRICE + + + 262 + STRING + + + 263 + CHAR + 0 + 1 + 2 + + + 264 + INT + + + 265 + INT + 0 + 1 + + + 266 + BOOLEAN + + + 267 + NUMINGROUP + + + 268 + NUMINGROUP + + + 269 + CHAR + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + A + B + C + D + E + F + G + H + J + K + L + M + N + O + P + Q + + + 270 + PRICE + + + 271 + QTY + + + 272 + UTCDATEONLY + + + 273 + UTCTIMEONLY + + + 274 + CHAR + 0 + 1 + 2 + 3 + + + 275 + EXCHANGE + + + 276 + MULTIPLESTRINGVALUE + A + B + C + D + E + F + G + H + I + L + J + K + M + N + O + P + Q + R + S + T + U + V + W + X + Y + Z + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + 0 + 1 + 2 + 1000 + 1001 + 1002 + 1003 + + + 277 + MULTIPLESTRINGVALUE + A + B + C + D + E + F + G + H + I + J + K + L + M + N + P + Q + R + Y + Z + S + T + U + V + W + X + 0 + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + AA + AB + AC + AD + AE + AF + AG + AH + AI + AJ + AK + AL + AM + AN + AO + AP + AQ + AR + AS + AT + 1000 + 1001 + 1002 + + + 278 + STRING + + + 279 + CHAR + 0 + 1 + 2 + 3 + 4 + + + 280 + STRING + + + 281 + CHAR + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + A + B + C + D + + + 282 + STRING + + + 283 + STRING + + + 284 + STRING + + + 285 + CHAR + 0 + 1 + + + 286 + MULTIPLECHARVALUE + 0 + 1 + 2 + 3 + 4 + 5 + + + 287 + INT + + + 288 + STRING + + + 289 + STRING + + + 290 + INT + + + 291 + MULTIPLECHARVALUE + 1 + 2 + 3 + + + 292 + MULTIPLECHARVALUE + A + B + C + D + E + F + G + H + I + J + K + L + M + N + O + P + Q + R + S + T + U + V + + + 293 + QTY + + + 294 + QTY + + + 295 + NUMINGROUP + + + 296 + NUMINGROUP + + + 297 + INT + 0 + 1 + 10 + 11 + 12 + 13 + 14 + 15 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 17 + + + 298 + INT + 1 + 2 + 3 + 4 + 5 + + + 299 + STRING + + + 300 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 99 + + + 301 + INT + 0 + 1 + 2 + + + 302 + STRING + + + 303 + INT + 1 + 2 + + + 304 + INT + + + 305 + STRING + + + 306 + STRING + + + 307 + STRING + + + 308 + EXCHANGE + + + 309 + STRING + + + 310 + STRING + + + 311 + STRING + + + 312 + STRING + + + 313 + MONTH-YEAR + + + 314 + DAY-OF-MONTH + + + 315 + INT + + + 316 + PRICE + + + 317 + CHAR + + + 318 + CURRENCY + + + 319 + QTY + + + 320 + STRING + + + 321 + INT + 0 + 1 + 2 + 3 + + + 322 + STRING + + + 323 + INT + 1 + 2 + 3 + 4 + 5 + 6 + + + 324 + STRING + + + 325 + BOOLEAN + + + 326 + INT + 1 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 2 + 20 + 21 + 22 + 23 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + + + 327 + CHAR + D + E + I + M + P + X + + + 328 + BOOLEAN + + + 329 + BOOLEAN + + + 330 + QTY + + + 331 + QTY + + + 332 + PRICE + + + 333 + PRICE + + + 334 + INT + 1 + 2 + 3 + + + 335 + STRING + + + 336 + STRING + + + 337 + STRING + + + 338 + INT + 1 + 2 + 3 + + + 339 + INT + 1 + 2 + 3 + + + 340 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + + + 341 + UTCTIMESTAMP + + + 342 + UTCTIMESTAMP + + + 343 + UTCTIMESTAMP + + + 344 + UTCTIMESTAMP + + + 345 + UTCTIMESTAMP + + + 346 + INT + + + 347 + STRING + ISO-2022-JP + EUC-JP + SHIFT_JIS + UTF-8 + + + 348 + LENGTH + + + 349 + DATA + + + 350 + LENGTH + + + 351 + DATA + + + 352 + LENGTH + + + 353 + DATA + + + 354 + LENGTH + + + 355 + DATA + + + 356 + LENGTH + + + 357 + DATA + + + 358 + LENGTH + + + 359 + DATA + + + 360 + LENGTH + + + 361 + DATA + + + 362 + LENGTH + + + 363 + DATA + + + 364 + LENGTH + + + 365 + DATA + + + 366 + PRICE + + + 367 + UTCTIMESTAMP + + + 368 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 99 + + + 369 + SEQNUM + + + 370 + UTCTIMESTAMP + + + 371 + INT + + + 372 + STRING + + + 373 + INT + 0 + 1 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 99 + 18 + + + 374 + CHAR + C + N + + + 375 + STRING + + + 376 + STRING + + + 377 + BOOLEAN + + + 378 + INT + 0 + 1 + 10 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 99 + 11 + 51 + 100 + + + 379 + STRING + + + 380 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 18 + + + 381 + AMT + + + 382 + NUMINGROUP + + + 383 + LENGTH + + + 384 + NUMINGROUP + + + 385 + CHAR + R + S + + + 386 + NUMINGROUP + + + 387 + QTY + + + 388 + CHAR + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + + + 389 + FLOAT + + + 390 + STRING + + + 391 + STRING + + + 392 + STRING + + + 393 + INT + + + 394 + INT + 1 + 2 + 3 + + + 395 + INT + + + 396 + AMT + + + 397 + AMT + + + 398 + NUMINGROUP + + + 399 + INT + 1 + 2 + 3 + + + 400 + STRING + + + 401 + INT + 1 + 2 + + + 402 + PERCENTAGE + + + 403 + PERCENTAGE + + + 404 + AMT + + + 405 + PERCENTAGE + + + 406 + AMT + + + 407 + PERCENTAGE + + + 408 + AMT + + + 409 + INT + 1 + 2 + 3 + 4 + + + 410 + PERCENTAGE + + + 411 + BOOLEAN + + + 412 + AMT + + + 413 + PERCENTAGE + + + 414 + INT + 1 + 2 + 3 + + + 415 + INT + + + 416 + INT + 1 + 2 + + + 417 + INT + + + 418 + CHAR + A + G + J + R + + + 419 + CHAR + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + A + B + C + D + Z + + + 420 + NUMINGROUP + + + 421 + COUNTRY + + + 422 + INT + + + 423 + INT + 1 + 10 + 11 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + + + 424 + QTY + + + 425 + QTY + + + 426 + PRICE + + + 427 + INT + 0 + 1 + 2 + + + 428 + NUMINGROUP + + + 429 + INT + 1 + 2 + 3 + 4 + 5 + 6 + + + 430 + INT + 1 + 2 + + + 431 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + + + 432 + LOCALMKTDATE + + + 433 + CHAR + 1 + 2 + 3 + 4 + 5 + + + 434 + CHAR + 1 + 2 + + + 435 + PERCENTAGE + + + 436 + FLOAT + + + 437 + QTY + + + 438 + UTCTIMESTAMP + + + 439 + STRING + + + 440 + STRING + + + 441 + INT + + + 442 + CHAR + 1 + 2 + 3 + + + 443 + UTCTIMESTAMP + + + 444 + STRING + + + 445 + LENGTH + + + 446 + DATA + + + 447 + CHAR + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + A + B + P + C + D + E + F + G + H + I + + + 448 + STRING + + + 449 + UTCDATEONLY + + + 450 + UTCTIMEONLY + + + 451 + PRICEOFFSET + + + 452 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 + 32 + 33 + 34 + 35 + 36 + 37 + 38 + 39 + 40 + 41 + 42 + 43 + 44 + 45 + 46 + 47 + 48 + 49 + 50 + 51 + 52 + 53 + 54 + 55 + 56 + 57 + 58 + 59 + 60 + 61 + 62 + 63 + 64 + 65 + 66 + 67 + 68 + 69 + 70 + 71 + 72 + 73 + 74 + 75 + 76 + 77 + 78 + 122 + + + 453 + NUMINGROUP + + + 454 + NUMINGROUP + + + 455 + STRING + + + 456 + STRING + + + 457 + NUMINGROUP + + + 458 + STRING + + + 459 + STRING + + + 460 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + + + 461 + STRING + + + 462 + INT + + + 463 + STRING + + + 464 + BOOLEAN + + + 465 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + + + 466 + STRING + + + 467 + STRING + + + 468 + CHAR + 0 + 1 + 2 + + + 469 + FLOAT + + + 470 + COUNTRY + + + 471 + STRING + + + 472 + STRING + + + 473 + NUMINGROUP + + + 474 + STRING + + + 475 + COUNTRY + + + 476 + STRING + + + 477 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + + + 478 + CURRENCY + + + 479 + CURRENCY + + + 480 + CHAR + N + M + O + Y + + + 481 + CHAR + Y + N + 1 + 2 + 3 + + + 482 + STRING + + + 483 + UTCTIMESTAMP + + + 484 + CHAR + B + C + D + E + O + P + Q + S + + + 485 + FLOAT + + + 486 + LOCALMKTDATE + + + 487 + INT + 0 + 1 + 2 + 3 + 4 + 5 + + + 488 + STRING + + + 489 + STRING + + + 490 + LOCALMKTDATE + + + 491 + STRING + + + 492 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + + + 493 + STRING + + + 494 + STRING + + + 495 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 999 + + + 496 + STRING + + + 497 + CHAR + N + Y + + + 498 + STRING + + + 499 + STRING + + + 500 + STRING + + + 501 + STRING + + + 502 + STRING + + + 503 + LOCALMKTDATE + + + 504 + LOCALMKTDATE + + + 505 + STRING + + + 506 + CHAR + A + R + H + N + + + 507 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 99 + + + 508 + STRING + + + 509 + STRING + + + 510 + NUMINGROUP + + + 511 + STRING + + + 512 + PERCENTAGE + + + 513 + STRING + + + 514 + CHAR + 0 + 1 + 2 + + + 515 + UTCTIMESTAMP + + + 516 + PERCENTAGE + + + 517 + CHAR + J + T + 2 + + + 518 + NUMINGROUP + + + 519 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + + + 520 + FLOAT + + + 521 + CURRENCY + + + 522 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + + + 523 + STRING + + + 524 + STRING + + + 525 + CHAR + + + 526 + STRING + + + 527 + STRING + + + 528 + CHAR + A + G + I + P + R + W + C + + + 529 + MULTIPLECHARVALUE + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + A + + + 530 + CHAR + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 9 + + + 531 + CHAR + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 9 + + + 532 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 99 + 100 + 101 + + + 533 + INT + + + 534 + INT + + + 535 + STRING + + + 536 + STRING + + + 537 + INT + 0 + 1 + 2 + 3 + + + 538 + INT + + + 539 + NUMINGROUP + + + 540 + AMT + + + 541 + LOCALMKTDATE + + + 542 + LOCALMKTDATE + + + 543 + STRING + + + 544 + CHAR + 1 + 2 + 3 + + + 545 + STRING + + + 546 + MULTIPLECHARVALUE + 1 + 2 + 3 + + + 547 + BOOLEAN + + + 548 + STRING + + + 549 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + + + 550 + INT + 0 + 1 + 2 + + + 551 + STRING + + + 552 + NUMINGROUP + 1 + 2 + + + 553 + STRING + + + 554 + STRING + + + 555 + NUMINGROUP + + + 556 + CURRENCY + + + 557 + INT + + + 558 + NUMINGROUP + + + 559 + INT + 0 + 1 + 2 + 3 + 4 + + + 560 + INT + 0 + 1 + 2 + 3 + 4 + 5 + + + 561 + QTY + + + 562 + QTY + + + 563 + INT + 0 + 1 + 2 + + + 564 + CHAR + + + 565 + INT + + + 566 + PRICE + + + 567 + INT + 1 + 99 + + + 568 + STRING + + + 569 + INT + 0 + 1 + 2 + 3 + 4 + + + 570 + BOOLEAN + + + 571 + STRING + + + 572 + STRING + + + 573 + CHAR + 0 + 1 + 2 + + + 574 + STRING + 1 + 2 + 3 + 4 + 5 + 6 + 7 + A1 + A2 + A3 + A4 + A5 + M3 + M4 + M5 + M6 + AQ + M1 + M2 + MT + S1 + S2 + S3 + S4 + S5 + 60 + 61 + 62 + 63 + 64 + 65 + + + 575 + BOOLEAN + + + 576 + NUMINGROUP + + + 577 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + + + 578 + STRING + + + 579 + STRING + + + 580 + INT + + + 581 + INT + 1 + 2 + 3 + 4 + 6 + 7 + 8 + + + 582 + INT + 1 + 2 + 3 + 4 + + + 583 + STRING + + + 584 + STRING + + + 585 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + + + 586 + UTCTIMESTAMP + + + 587 + CHAR + + + 588 + LOCALMKTDATE + + + 589 + CHAR + 0 + 1 + 2 + + + 590 + CHAR + 0 + 1 + 2 + + + 591 + CHAR + 0 + 1 + + + 592 + COUNTRY + + + 593 + STRING + + + 594 + STRING + + + 595 + STRING + + + 596 + COUNTRY + + + 597 + STRING + + + 598 + STRING + + + 599 + STRING + + + 600 + STRING + + + 601 + STRING + + + 602 + STRING + + + 603 + STRING + + + 604 + STRING + + + 605 + STRING + + + 606 + STRING + + + 607 + INT + + + 608 + STRING + + + 609 + STRING + + + 610 + MONTH-YEAR + + + 611 + LOCALMKTDATE + + + 612 + PRICE + + + 613 + CHAR + + + 614 + FLOAT + + + 615 + PERCENTAGE + + + 616 + EXCHANGE + + + 617 + STRING + + + 618 + LENGTH + + + 619 + DATA + + + 620 + STRING + + + 621 + LENGTH + + + 622 + DATA + + + 623 + FLOAT + + + 624 + CHAR + + + 625 + STRING + + + 626 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + + 627 + NUMINGROUP + + + 628 + STRING + + + 629 + UTCTIMESTAMP + + + 630 + SEQNUM + + + 631 + PRICE + + + 632 + PERCENTAGE + + + 633 + PERCENTAGE + + + 634 + PERCENTAGE + + + 635 + STRING + B + C + E + F + H + I + L + M + 1 + 2 + 3 + 4 + 5 + 9 + + + 636 + BOOLEAN + + + 637 + PRICE + + + 638 + INT + 0 + 1 + + + 639 + PRICEOFFSET + + + 640 + PRICE + + + 641 + PRICEOFFSET + + + 642 + PRICEOFFSET + + + 643 + PRICEOFFSET + + + 644 + STRING + + + 645 + PRICE + + + 646 + PRICE + + + 647 + QTY + + + 648 + QTY + + + 649 + STRING + + + 650 + BOOLEAN + + + 651 + PRICE + + + 652 + QTY + + + 653 + INT + 0 + 1 + 2 + 3 + 4 + + + 654 + STRING + + + 655 + STRING + + + 656 + FLOAT + + + 657 + FLOAT + + + 658 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 99 + + + 659 + STRING + + + 660 + INT + 1 + 2 + 3 + 4 + 5 + 99 + + + 661 + INT + + + 662 + PRICE + + + 663 + INT + + + 664 + STRING + + + 665 + INT + 1 + 2 + 3 + 4 + 5 + + + 666 + INT + 0 + 1 + 2 + + + 667 + MONTH-YEAR + + + 668 + INT + 1 + 2 + + + 669 + PRICE + + + 670 + NUMINGROUP + + + 671 + STRING + + + 672 + STRING + + + 673 + QTY + + + 674 + STRING + + + 675 + CURRENCY + + + 676 + CURRENCY + + + 677 + STRING + + + 678 + STRING + + + 679 + PRICE + + + 680 + INT + + + 681 + PRICE + + + 682 + STRING + + + 683 + NUMINGROUP + + + 684 + PRICE + + + 685 + QTY + + + 686 + INT + + + 687 + QTY + + + 688 + STRING + + + 689 + STRING + + + 690 + INT + 1 + 2 + 4 + 5 + + + 691 + STRING + + + 692 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + + + 693 + STRING + + + 694 + INT + 1 + 2 + 3 + 4 + 5 + 6 + + + 695 + CHAR + + + 696 + LOCALMKTDATE + + + 697 + PRICE + + + 698 + INT + + + 699 + STRING + + + 700 + BOOLEAN + + + 701 + LOCALMKTDATE + + + 702 + NUMINGROUP + + + 703 + STRING + TQ + IAS + IES + FIN + SOD + EX + AS + TX + TA + PIT + TRF + ETR + ALC + PA + ASF + DLV + TOT + XM + SPL + RCV + CAA + DN + EP + + + 704 + QTY + + + 705 + QTY + + + 706 + INT + 0 + 1 + 2 + + + 707 + STRING + FMTM + IMTM + TVAR + SMTM + PREM + CRES + CASH + VADJ + SETL + + + 708 + AMT + + + 709 + INT + 1 + 2 + 3 + 4 + 5 + 6 + + + 710 + STRING + + + 711 + NUMINGROUP + + + 712 + INT + 1 + 2 + 3 + 4 + + + 713 + STRING + + + 714 + STRING + + + 715 + LOCALMKTDATE + + + 716 + STRING + ITD + RTH + ETH + EOD + + + 717 + STRING + + + 718 + INT + 0 + 1 + 2 + 3 + + + 719 + BOOLEAN + + + 720 + BOOLEAN + + + 721 + STRING + + + 722 + INT + 0 + 1 + 2 + 3 + 4 + + + 723 + INT + 0 + 1 + 99 + + + 724 + INT + 0 + 1 + 2 + 3 + 4 + 5 + + + 725 + INT + 0 + 1 + + + 726 + STRING + + + 727 + INT + + + 728 + INT + 0 + 1 + 2 + 3 + 4 + 99 + + + 729 + INT + 0 + 1 + 2 + + + 730 + PRICE + + + 731 + INT + 1 + 2 + + + 732 + PRICE + + + 733 + INT + + + 734 + PRICE + + + 735 + NUMINGROUP + + + 736 + CURRENCY + + + 737 + AMT + + + 738 + AMT + + + 739 + LOCALMKTDATE + + + 740 + STRING + + + 741 + AMT + + + 742 + AMT + + + 743 + LOCALMKTDATE + + + 744 + CHAR + R + P + + + 745 + QTY + + + 746 + AMT + + + 747 + CHAR + A + M + + + 748 + INT + + + 749 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 8 + 9 + 99 + 100 + 200 + + + 750 + INT + 0 + 1 + 2 + + + 751 + INT + 0 + 1 + 2 + 3 + 4 + 99 + + + 752 + INT + 1 + 2 + 3 + + + 753 + NUMINGROUP + + + 754 + BOOLEAN + + + 755 + STRING + + + 756 + NUMINGROUP + + + 757 + STRING + + + 758 + CHAR + + + 759 + INT + + + 760 + STRING + + + 761 + STRING + + + 762 + STRING + + + 763 + STRING + + + 764 + STRING + + + 765 + PERCENTAGE + + + 766 + AMT + + + 767 + CURRENCY + + + 768 + NUMINGROUP + + + 769 + UTCTIMESTAMP + + + 770 + INT + 1 + 2 + 3 + 4 + 5 + 6 + + + 771 + STRING + + + 772 + STRING + + + 773 + INT + 1 + 2 + 3 + + + 774 + INT + 1 + 2 + 99 + + + 775 + INT + 0 + 1 + 2 + + + 776 + INT + + + 777 + STRING + + + 778 + NUMINGROUP + + + 779 + UTCTIMESTAMP + + + 780 + INT + 0 + 1 + 2 + 3 + 4 + + + 781 + NUMINGROUP + + + 782 + STRING + + + 783 + CHAR + + + 784 + INT + + + 785 + STRING + + + 786 + INT + + + 787 + CHAR + S + C + + + 788 + INT + 1 + 2 + 3 + 4 + + + 789 + SEQNUM + + + 790 + STRING + + + 791 + STRING + + + 792 + INT + 0 + 1 + 2 + 99 + + + 793 + STRING + + + 794 + INT + 3 + 4 + 5 + 8 + 2 + 9 + 10 + 11 + 12 + 14 + + + 795 + STRING + + + 796 + INT + 1 + 2 + 99 + + + 797 + BOOLEAN + + + 798 + INT + 1 + 2 + 3 + 4 + 6 + 7 + 8 + + + 799 + PRICE + + + 800 + QTY + + + 801 + NUMINGROUP + + + 802 + NUMINGROUP + + + 803 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 + 32 + + + 804 + NUMINGROUP + + + 805 + INT + + + 806 + NUMINGROUP + + + 807 + INT + + + 808 + INT + 1 + 2 + 3 + 4 + 5 + 6 + + + 810 + PRICE + + + 811 + FLOAT + + + 812 + INT + + + 813 + INT + + + 814 + INT + 0 + 1 + 2 + 3 + + + 815 + INT + 0 + 1 + 2 + 3 + + + 816 + NUMINGROUP + + + 817 + STRING + + + 818 + STRING + + + 819 + INT + 0 + 1 + 2 + + + 820 + STRING + + + 821 + STRING + + + 822 + STRING + + + 823 + STRING + + + 824 + STRING + + + 825 + STRING + + + 826 + INT + 0 + 1 + 2 + 3 + 4 + 5 + + + 827 + INT + 0 + 1 + + + 828 + INT + 0 + 1 + 10 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 + 32 + 33 + 34 + 35 + 36 + 37 + 38 + 39 + 40 + 41 + 42 + 43 + 44 + 45 + 46 + 47 + 54 + + + 829 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 + 32 + 1000 + 1004 + 1005 + 1006 + 1007 + 1008 + 1009 + 1010 + 1011 + 1012 + 1013 + 2001 + 3001 + 1018 + 1019 + 1020 + 1021 + 1022 + 1023 + 1024 + 1025 + 1026 + 1027 + 1028 + 1029 + 2002 + 1031 + 1032 + 1033 + + + 830 + STRING + + + 831 + STRING + + + 832 + INT + + + 833 + STRING + + + 834 + PRICEOFFSET + + + 835 + INT + 0 + 1 + + + 836 + INT + 0 + 1 + 2 + 3 + + + 837 + INT + 0 + 1 + 2 + + + 838 + INT + 1 + 2 + + + 839 + PRICE + + + 840 + INT + 1 + 2 + 3 + 4 + + + 841 + INT + 0 + 1 + + + 842 + INT + 0 + 1 + 2 + 3 + + + 843 + INT + 0 + 1 + 2 + + + 844 + INT + 1 + 2 + + + 845 + PRICE + + + 846 + INT + 1 + 2 + 3 + 4 + + + 847 + INT + 1 + 2 + 3 + + + 848 + STRING + + + 849 + PERCENTAGE + + + 850 + FLOAT + + + 851 + INT + 1 + 2 + 3 + + + 852 + BOOLEAN + + + 853 + INT + 0 + 1 + 2 + 3 + 4 + 5 + + + 854 + INT + 0 + 1 + 2 + + + 855 + INT + + + 856 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + + + 857 + INT + 0 + 1 + + + 858 + AMT + + + 859 + STRING + + + 860 + PRICE + + + 861 + PRICE + + + 862 + NUMINGROUP + + + 863 + QTY + + + 864 + NUMINGROUP + + + 865 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 99 + + + 866 + LOCALMKTDATE + + + 867 + PRICE + + + 868 + STRING + + + 869 + PERCENTAGE + + + 870 + NUMINGROUP + + + 871 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 99 + + + 872 + STRING + + + 873 + LOCALMKTDATE + + + 874 + LOCALMKTDATE + + + 875 + INT + 1 + 2 + 99 + + + 876 + STRING + + + 877 + STRING + + + 878 + STRING + + + 879 + QTY + + + 880 + STRING + + + 881 + STRING + + + 882 + PRICE + + + 883 + PRICE + + + 884 + AMT + + + 885 + AMT + + + 886 + AMT + + + 887 + NUMINGROUP + + + 888 + STRING + + + 889 + STRING + + + 890 + AMT + + + 891 + INT + 0 + 1 + 2 + + + 892 + INT + + + 893 + BOOLEAN + + + 894 + STRING + + + 895 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + + + 896 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + + + 897 + NUMINGROUP + + + 898 + PERCENTAGE + + + 899 + AMT + + + 900 + AMT + + + 901 + AMT + + + 902 + STRING + + + 903 + INT + 0 + 1 + 2 + 3 + 4 + + + 904 + STRING + + + 905 + INT + 0 + 1 + 2 + 3 + + + 906 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 99 + + + 907 + STRING + + + 908 + STRING + + + 909 + STRING + + + 910 + INT + 0 + 1 + 2 + 3 + 4 + + + 911 + INT + + + 912 + BOOLEAN + + + 913 + STRING + + + 914 + STRING + + + 915 + LOCALMKTDATE + + + 916 + LOCALMKTDATE + + + 917 + LOCALMKTDATE + + + 918 + CURRENCY + + + 919 + INT + 0 + 1 + 2 + 3 + + + 920 + AMT + + + 921 + AMT + + + 922 + AMT + + + 923 + STRING + + + 924 + INT + 1 + 2 + 3 + 4 + + + 925 + STRING + + + 926 + INT + 1 + 2 + 3 + 4 + 5 + 6 + + + 927 + STRING + + + 928 + INT + 1 + 2 + 3 + 4 + + + 929 + STRING + + + 930 + STRING + + + 931 + STRING + + + 932 + STRING + + + 933 + STRING + + + 934 + STRING + + + 935 + INT + 1 + 2 + 4 + 8 + + + 936 + NUMINGROUP + + + 937 + INT + 1 + 2 + + + 938 + NUMINGROUP + + + 939 + INT + 0 + 1 + 3 + + + 940 + INT + 1 + 2 + 3 + + + 941 + CURRENCY + + + 942 + CURRENCY + + + 943 + STRING + + + 944 + INT + 0 + 1 + 2 + + + 945 + INT + 0 + 1 + 2 + 3 + 4 + + + 946 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 99 + + + 947 + CURRENCY + + + 948 + NUMINGROUP + + + 949 + STRING + + + 950 + CHAR + + + 951 + INT + + + 952 + NUMINGROUP + + + 953 + STRING + + + 954 + INT + + + 955 + MONTH-YEAR + + + 956 + LOCALMKTDATE + + + 957 + NUMINGROUP + + + 958 + STRING + + + 959 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + + + 960 + STRING + + + 961 + STRING + + + 962 + UTCTIMESTAMP + + + 963 + INT + + + 964 + INT + + + 965 + STRING + 1 + 2 + + + 966 + STRING + + + 967 + FLOAT + + + 968 + FLOAT + + + 969 + FLOAT + + + 970 + INT + + + 971 + INT + + + 972 + PERCENTAGE + + + 973 + AMT + + + 974 + STRING + FIXED + DIFF + + + 975 + INT + 2 + 4 + 5 + + + 976 + LOCALMKTDATE + + + 977 + STRING + + + 978 + BOOLEAN + + + 979 + STRING + + + 980 + CHAR + A + D + M + + + 981 + NUMINGROUP + + + 982 + INT + 1 + 2 + 3 + 4 + 5 + + + 983 + QTY + + + 984 + NUMINGROUP + + + 985 + AMT + + + 986 + AMT + + + 987 + LOCALMKTDATE + + + 988 + STRING + + + 989 + STRING + + + 990 + STRING + + + 991 + PRICE + + + 992 + INT + 1 + 2 + + + 993 + STRING + + + 994 + STRING + + + 996 + STRING + MWh + MMBtu + Bbl + Gal + t + tn + MMbbl + lbs + oz_tr + USD + Bcf + Bu + + + 997 + STRING + S + Min + H + D + Wk + Mo + Yr + + + 998 + STRING + + + 999 + STRING + + + 1000 + STRING + + + 1001 + STRING + + + 1002 + INT + 1 + 2 + 3 + + + 1003 + STRING + + + 1005 + STRING + + + 1006 + STRING + + + 1007 + STRING + + + 1008 + INT + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + + + 1009 + INT + + + 1011 + STRING + + + 1012 + UTCTIMESTAMP + + + 1013 + INT + + + 1014 + STRING + + + 1015 + CHAR + 0 + 1 + + + 1016 + NUMINGROUP + + + 1017 + FLOAT + + + 1018 + NUMINGROUP + + + 1019 + STRING + + + 1020 + QTY + + + 1021 + INT + 1 + 2 + 3 + + + 1022 + STRING + + + 1023 + INT + + + 1024 + INT + 0 + 1 + 2 + + + 1025 + PRICE + + + 1026 + FLOAT + + + 1027 + PRICEOFFSET + + + 1028 + BOOLEAN + + + 1029 + BOOLEAN + + + 1030 + STRING + + + 1031 + MULTIPLESTRINGVALUE + ADD + AON + CNH + DIR + E.W + FOK + IO + IOC + LOO + LOC + MAO + MAC + MOO + MOC + MQT + NH + OVD + PEG + RSV + S.W + SCL + TMO + TS + WRK + + + 1032 + INT + 1 + + + 1033 + STRING + A + AR + D + IN + IS + O + PF + PR + PT + S + T + + + 1034 + INT + 1 + + + 1035 + MULTIPLESTRINGVALUE + ADD + AON + CNH + DIR + E.W + FOK + IO + IOC + LOO + LOC + MAO + MAC + MOO + MOC + MQT + NH + OVD + PEG + RSV + S.W + SCL + TMO + TS + WRK + + + 1036 + CHAR + 0 + 1 + 2 + + + 1037 + AMT + + + 1038 + AMT + + + 1039 + STRING + + + 1040 + STRING + + + 1041 + STRING + + + 1042 + STRING + + + 1043 + INT + 0 + 1 + + + 1044 + QTY + + + 1045 + FLOAT + + + 1046 + CHAR + M + D + + + 1047 + CHAR + O + C + R + F + + + 1048 + PRICEOFFSET + + + 1049 + CHAR + + + 1050 + CHAR + + + 1051 + INT + + + 1052 + NUMINGROUP + + + 1053 + STRING + + + 1054 + INT + + + 1055 + STRING + + + 1056 + QTY + + + 1057 + BOOLEAN + + + 1058 + NUMINGROUP + + + 1059 + STRING + + + 1060 + CHAR + + + 1061 + INT + + + 1062 + NUMINGROUP + + + 1063 + STRING + + + 1064 + INT + + + 1065 + PRICEOFFSET + + + 1066 + PRICEOFFSET + + + 1067 + PRICEOFFSET + + + 1068 + PRICEOFFSET + + + 1069 + PRICEOFFSET + + + 1070 + INT + 0 + 1 + 2 + 3 + 4 + + + 1071 + PRICEOFFSET + + + 1072 + AMT + + + 1073 + PRICEOFFSET + + + 1074 + QTY + + + 1075 + AMT + + + 1079 + TZTIMEONLY + + + 1080 + STRING + + + 1081 + CHAR + 0 + 1 + 2 + 3 + + + 1082 + QTY + + + 1083 + CHAR + 1 + 2 + + + 1084 + CHAR + 1 + 2 + 3 + 4 + + + 1085 + QTY + + + 1086 + QTY + + + 1087 + QTY + + + 1088 + QTY + + + 1089 + QTY + + + 1090 + INT + + + 1091 + BOOLEAN + + + 1092 + CHAR + 0 + 1 + 2 + 3 + + + 1093 + CHAR + 1 + 2 + 3 + + + 1094 + INT + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + + + 1095 + PRICE + + + 1096 + STRING + + + 1097 + STRING + + + 1098 + STRING + + + 1099 + STRING + + + 1100 + CHAR + 1 + 2 + 3 + 4 + + + 1101 + CHAR + 1 + 2 + 3 + + + 1102 + PRICE + + + 1103 + STRING + + + 1104 + STRING + + + 1105 + STRING + + + 1106 + STRING + + + 1107 + CHAR + 1 + 2 + 3 + 4 + 5 + 6 + + + 1108 + CHAR + 0 + 1 + 2 + 3 + + + 1109 + CHAR + U + D + + + 1110 + PRICE + + + 1111 + CHAR + 1 + 2 + + + 1112 + QTY + + + 1113 + STRING + + + 1114 + STRING + + + 1115 + CHAR + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + + + 1116 + NUMINGROUP + + + 1117 + STRING + + + 1118 + CHAR + + + 1119 + INT + + + 1120 + NUMINGROUP + + + 1121 + STRING + + + 1122 + INT + + + 1123 + CHAR + 0 + 1 + 2 + 3 + 4 + + + 1124 + CHAR + + + 1125 + LOCALMKTDATE + + + 1126 + STRING + + + 1127 + STRING + + + 1128 + STRING + 9 + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + + + 1129 + STRING + + + 1130 + STRING + + + 1131 + STRING + + + 1132 + TZTIMESTAMP + + + 1133 + CHAR + B + C + D + E + G + + + 1134 + BOOLEAN + + + 1135 + STRING + + + 1136 + STRING + + + 1137 + STRING + 9 + + + 1138 + QTY + + + 1139 + STRING + + + 5450 + INT + 1 + 2 + 11 + 12 + 45 + 46 + 47 + 48 + 49 + 50 + 51 + 52 + 53 + 54 + + + 8000 + UTCTIMESTAMP + + + 9000 + INT + + + 9001 + STRING + + + 9002 + STRING + + + 9995 + LOCALMKTDATE + + + 1167 + CHAR + 5 + + + 1166 + STRING + + + 1180 + STRING + + + 1181 + SEQNUM + + + 1182 + SEQNUM + + + 1183 + SEQNUM + + + 1300 + STRING + + + 1301 + STRING + + + 1328 + STRING + + + 1346 + STRING + + + 1347 + STRING + + + 1350 + SEQNUM + + + 1351 + NUMINGROUP + + + 1352 + BOOLEAN + + + 1353 + STRING + + + 1354 + INT + 0 + 1 + 2 + + + 1355 + STRING + + + 1357 + SEQNUM + + + 1369 + STRING + + + 1427 + STRING + + + 1444 + INT + 1 + 2 + 4 + + + 1461 + NUMINGROUP + + + 1462 + STRING + + + 1463 + CHAR + D + + + 1464 + INT + 1 + 76 + + + 9730 + CHAR + A + R + C + + + 20000 + CHAR + 0 + 1 + 2 + + + 20100 + PRICE + + + 20110 + INT + 0 + 1 + + + 20111 + INT + 0 + 1 + + + 27010 + INT + 0 + 99 + 100 + 1 + 2 + 3 + + + + + Group + 1461 + NUMINGROUP + + + + + + Group + 552 + NUMINGROUP + + + + + + + + + Group + 453 + NUMINGROUP + + + + + + Group + 552 + NUMINGROUP + + + + + + + Group + 453 + NUMINGROUP + + + + + + Group + 1461 + NUMINGROUP + + + + + + Group + 295 + NUMINGROUP + + + + + Group + 296 + NUMINGROUP + + + + + Group + 295 + NUMINGROUP + + + + + + + + Group + 454 + NUMINGROUP + + + + + Group + 552 + NUMINGROUP + + + + + + + + + + + + + + + + Group + 453 + NUMINGROUP + + + + + + Group + 454 + NUMINGROUP + + + + + Group + 453 + NUMINGROUP + + + + + + Group + 1351 + NUMINGROUP + + + + + + Group + 1351 + NUMINGROUP + + + + + + + + Group + 453 + NUMINGROUP + + + + + + Group + 453 + NUMINGROUP + + + + + + Group + 386 + NUMINGROUP + + + + Group + 73 + NUMINGROUP + + + + + + Group + 398 + NUMINGROUP + + + + Group + 73 + NUMINGROUP + + + + Message + false + D + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Message + false + 8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Message + false + TEST_1 + + + + Message + false + TEST_2 + + + + Message + false + TEST_3 + + + + Component + + + + + Component + + + + + Component + + + + Component + + + + Component + + + + Component + + + + Component + + + + Header + + + + + + + + + + + + + + + + Trailer + + + + Message + true + A + + + + + + + + + + + + + Message + true + 5 + + + + + + + Message + true + 0 + + + + + + Message + true + 1 + + + + + + Message + true + 2 + + + + + + + Message + true + 3 + + + + + + + + + + Message + true + 4 + + + + + + + \ No newline at end of file diff --git a/suppressions.xml b/suppressions.xml new file mode 100644 index 0000000..0c032ab --- /dev/null +++ b/suppressions.xml @@ -0,0 +1,10 @@ + + + + + + + ^pkg:maven/com\.exactpro\.th2/grpc-.*@.*$ + cpe:/a:grpc:grpc + + \ No newline at end of file