├── .gitignore
├── LICENSE
├── README.md
├── kaitai_struct_java_runtime.iml
├── pom.xml
└── src
├── main
└── java
│ └── io
│ └── kaitai
│ └── struct
│ ├── ByteBufferKaitaiStream.java
│ ├── ConsistencyError.java
│ ├── CustomDecoder.java
│ ├── CustomProcessor.java
│ ├── KaitaiStream.java
│ ├── KaitaiStruct.java
│ ├── RandomAccessFileKaitaiStream.java
│ └── annotations
│ ├── Generated.java
│ ├── Instance.java
│ ├── Parameter.java
│ └── SeqItem.java
└── test
└── java
└── io
└── kaitai
└── struct
├── ByteBufferKaitaiStreamTest.java
├── KaitaiStreamTest.java
└── RandomAccessFileKaitaiStreamTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | pom.xml.tag
3 | pom.xml.releaseBackup
4 | pom.xml.versionsBackup
5 | pom.xml.next
6 | release.properties
7 | dependency-reduced-pom.xml
8 | buildNumber.properties
9 | .mvn/timing.properties
10 |
11 | # Exclude maven wrapper
12 | !/.mvn/wrapper/maven-wrapper.jar
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2015-2025 Kaitai Project
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Kaitai Struct: runtime library for Java
2 |
3 | [](https://central.sonatype.com/artifact/io.kaitai/kaitai-struct-runtime)
4 |
5 | This library implements Kaitai Struct API for Java.
6 |
7 | Kaitai Struct is a declarative language used for describe various binary
8 | data structures, laid out in files or in memory: i.e. binary file
9 | formats, network stream packet formats, etc.
10 |
11 | Further reading:
12 |
13 | * [About Kaitai Struct](https://kaitai.io/)
14 | * [About API implemented in this library](https://doc.kaitai.io/stream_api.html)
15 | * [Java-specific notes](https://doc.kaitai.io/lang_java.html)
16 |
--------------------------------------------------------------------------------
/kaitai_struct_java_runtime.iml:
--------------------------------------------------------------------------------
1 |
2 |
96 | * This way one can access the underlying raw bytes associated with this structure, but it is 97 | * important to note that the caller needs to know what this raw data is: depending on the 98 | * hierarchy of user types, how the format has been described and how a user type is actually 99 | * used, it might be that one accesses all data of some format or only a special substream 100 | * view of it. We can't know currently, so one needs to keep that in mind when authoring a KSY 101 | * and e.g. use substreams with user types whenever such a type most likely needs to access its 102 | * underlying raw data. Using a substream in KSY and directly passing some raw data to a user 103 | * type outside of normal KS parse order is equivalent and will provide the same results. If no 104 | * substream is used instead, the here provided data might differ depending on the context in 105 | * which the associated type was parsed, because the underlying {@link ByteBuffer} might 106 | * contain the data of all parent types and such as well and not only the one the caller is 107 | * actually interested in. 108 | *
109 | *110 | * The returned {@link ByteBuffer} is always rewound to position 0, because this stream was 111 | * most likely used to parse a type already, in which case the former position would have been 112 | * at the end of the buffer. Such a position doesn't help a common reading user much and that 113 | * fact can easily be forgotten, repositioning to another index than the start is pretty easy 114 | * as well. Rewinding/repositioning doesn't even harm performance in any way. 115 | *
116 | * @return read-only {@link ByteBuffer} to access raw data for the associated type. 117 | */ 118 | public ByteBuffer asRoBuffer() { 119 | ByteBuffer retVal = bb.asReadOnlyBuffer(); 120 | retVal.rewind(); 121 | 122 | return retVal; 123 | } 124 | 125 | /** 126 | * Closes the stream safely, writing the buffered bits to the underlying byte stream 127 | * first (if applicable). If there was an open file associated with the stream, closes 128 | * that file. 129 | *130 | * If the last read/write/seek operation in the stream was {@link #writeBitsIntBe(int, long)} or 131 | * {@link #writeBitsIntLe(int, long)} and the stream ended at an unaligned bit 132 | * position (i.e. not at a byte boundary), writes a final byte with buffered bits to 133 | * the underlying stream before closing the stream. 134 | *
135 | *136 | * Regardless of whether the closure is successful or not, always relinquishes the 137 | * underlying resources. In accordance with {@link java.io.Closeable#close()}, 138 | * subsequent calls have no effect. Once this method has been called, read and write 139 | * operations, seeking or accessing the state using {@link #pos()}, {@link #size()} or 140 | * {@link #isEof()} on this stream will typically throw a {@link NullPointerException}. 141 | *
142 | * @implNote 143 | *
144 | * Unfortunately, there is no simple way to close memory-mapped {@link ByteBuffer} in
145 | * Java and unmap underlying file. As {@link MappedByteBuffer} documentation suggests,
146 | * "mapped byte buffer and the file mapping that it represents remain valid until the
147 | * buffer itself is garbage-collected". Thus, the best we can do is to delete all
148 | * references to it, which breaks all subsequent read..
methods with
149 | * {@link NullPointerException}. Afterwards, a call to {@link System#gc()} will
150 | * typically release the mmap, if garbage collection will be triggered.
151 | *
153 | * There is a JDK-4724038 request 154 | * for adding unmap method filed at Java bugtracker since 2002, but as of 2018, it 155 | * is still unresolved. 156 | *
157 | *158 | * A couple of unsafe approaches (such as using JNI, or using reflection to invoke JVM 159 | * internal APIs) have been suggested and used with some success, but these are either 160 | * unportable or dangerous (may crash JVM), so we're not using them in this general 161 | * purpose code. 162 | *
163 | *164 | * For more examples and suggestions, see: 165 | * How to unmap a file from memory 166 | * mapped using FileChannel in java? 167 | *
168 | * @throws IOException if {@link FileChannel} can't be closed 169 | */ 170 | @Override 171 | public void close() throws IOException { 172 | Exception exc = null; 173 | try { 174 | if (bitsWriteMode) { 175 | writeAlignToByte(); 176 | } else { 177 | alignToByte(); 178 | } 179 | } catch (Exception e) { 180 | exc = e; 181 | throw e; 182 | } finally { 183 | bb = null; 184 | if (fc != null) try { 185 | fc.close(); 186 | } catch (IOException e) { 187 | if (exc != null) { 188 | // deliver FileChannel.close() exception as primary, the one from 189 | // writeAlignToByte() as suppressed 190 | e.addSuppressed(exc); 191 | } 192 | throw e; 193 | } finally { 194 | fc = null; 195 | } 196 | } 197 | } 198 | 199 | //region Stream positioning 200 | 201 | @Override 202 | public boolean isEof() { 203 | return !(bb.hasRemaining() || (!bitsWriteMode && bitsLeft > 0)); 204 | } 205 | 206 | @Override 207 | public void seek(int newPos) { 208 | if (bitsWriteMode) { 209 | writeAlignToByte(); 210 | } else { 211 | alignToByte(); 212 | } 213 | bb.position(newPos); 214 | } 215 | 216 | @Override 217 | public void seek(long newPos) { 218 | if (newPos > Integer.MAX_VALUE) { 219 | throw new IllegalArgumentException("Java ByteBuffer can't be seeked past Integer.MAX_VALUE"); 220 | } 221 | seek((int) newPos); 222 | } 223 | 224 | @Override 225 | public int pos() { 226 | return bb.position() + ((bitsWriteMode && bitsLeft > 0) ? 1 : 0); 227 | } 228 | 229 | @Override 230 | public long size() { 231 | return bb.limit(); 232 | } 233 | 234 | //endregion 235 | 236 | //region Reading 237 | 238 | //region Integer numbers 239 | 240 | //region Signed 241 | 242 | /** 243 | * Reads one signed 1-byte integer, returning it properly as Java's "byte" type. 244 | * @return 1-byte integer read from a stream 245 | */ 246 | @Override 247 | public byte readS1() { 248 | alignToByte(); 249 | return bb.get(); 250 | } 251 | 252 | //region Big-endian 253 | 254 | @Override 255 | public short readS2be() { 256 | alignToByte(); 257 | bb.order(ByteOrder.BIG_ENDIAN); 258 | return bb.getShort(); 259 | } 260 | 261 | @Override 262 | public int readS4be() { 263 | alignToByte(); 264 | bb.order(ByteOrder.BIG_ENDIAN); 265 | return bb.getInt(); 266 | } 267 | 268 | @Override 269 | public long readS8be() { 270 | alignToByte(); 271 | bb.order(ByteOrder.BIG_ENDIAN); 272 | return bb.getLong(); 273 | } 274 | 275 | //endregion 276 | 277 | //region Little-endian 278 | 279 | @Override 280 | public short readS2le() { 281 | alignToByte(); 282 | bb.order(ByteOrder.LITTLE_ENDIAN); 283 | return bb.getShort(); 284 | } 285 | 286 | @Override 287 | public int readS4le() { 288 | alignToByte(); 289 | bb.order(ByteOrder.LITTLE_ENDIAN); 290 | return bb.getInt(); 291 | } 292 | 293 | @Override 294 | public long readS8le() { 295 | alignToByte(); 296 | bb.order(ByteOrder.LITTLE_ENDIAN); 297 | return bb.getLong(); 298 | } 299 | 300 | //endregion 301 | 302 | //endregion 303 | 304 | //region Unsigned 305 | 306 | @Override 307 | public int readU1() { 308 | alignToByte(); 309 | return bb.get() & 0xff; 310 | } 311 | 312 | //region Big-endian 313 | 314 | @Override 315 | public int readU2be() { 316 | alignToByte(); 317 | bb.order(ByteOrder.BIG_ENDIAN); 318 | return bb.getShort() & 0xffff; 319 | } 320 | 321 | @Override 322 | public long readU4be() { 323 | alignToByte(); 324 | bb.order(ByteOrder.BIG_ENDIAN); 325 | return bb.getInt() & 0xffffffffL; 326 | } 327 | 328 | //endregion 329 | 330 | //region Little-endian 331 | 332 | @Override 333 | public int readU2le() { 334 | alignToByte(); 335 | bb.order(ByteOrder.LITTLE_ENDIAN); 336 | return bb.getShort() & 0xffff; 337 | } 338 | 339 | @Override 340 | public long readU4le() { 341 | alignToByte(); 342 | bb.order(ByteOrder.LITTLE_ENDIAN); 343 | return bb.getInt() & 0xffffffffL; 344 | } 345 | 346 | //endregion 347 | 348 | //endregion 349 | 350 | //endregion 351 | 352 | //region Floating point numbers 353 | 354 | //region Big-endian 355 | 356 | @Override 357 | public float readF4be() { 358 | alignToByte(); 359 | bb.order(ByteOrder.BIG_ENDIAN); 360 | return bb.getFloat(); 361 | } 362 | 363 | @Override 364 | public double readF8be() { 365 | alignToByte(); 366 | bb.order(ByteOrder.BIG_ENDIAN); 367 | return bb.getDouble(); 368 | } 369 | 370 | //endregion 371 | 372 | //region Little-endian 373 | 374 | @Override 375 | public float readF4le() { 376 | alignToByte(); 377 | bb.order(ByteOrder.LITTLE_ENDIAN); 378 | return bb.getFloat(); 379 | } 380 | 381 | @Override 382 | public double readF8le() { 383 | alignToByte(); 384 | bb.order(ByteOrder.LITTLE_ENDIAN); 385 | return bb.getDouble(); 386 | } 387 | 388 | //endregion 389 | 390 | //endregion 391 | 392 | //region Byte arrays 393 | 394 | @Override 395 | protected byte[] readBytesNotAligned(long n) { 396 | byte[] buf = new byte[toByteArrayLength(n)]; 397 | bb.get(buf); 398 | return buf; 399 | } 400 | 401 | /** 402 | * Reads all the remaining bytes in a stream as byte array. 403 | * @return all remaining bytes in a stream as byte array 404 | */ 405 | @Override 406 | public byte[] readBytesFull() { 407 | alignToByte(); 408 | byte[] buf = new byte[bb.remaining()]; 409 | bb.get(buf); 410 | return buf; 411 | } 412 | 413 | @Override 414 | public byte[] readBytesTerm(byte term, boolean includeTerm, boolean consumeTerm, boolean eosError) { 415 | alignToByte(); 416 | ByteArrayOutputStream buf = new ByteArrayOutputStream(); 417 | while (true) { 418 | if (!bb.hasRemaining()) { 419 | if (eosError) { 420 | throw new RuntimeException("End of stream reached, but no terminator " + term + " found"); 421 | } 422 | return buf.toByteArray(); 423 | } 424 | byte c = bb.get(); 425 | if (c == term) { 426 | if (includeTerm) 427 | buf.write(c); 428 | if (!consumeTerm) 429 | bb.position(bb.position() - 1); 430 | return buf.toByteArray(); 431 | } 432 | buf.write(c); 433 | } 434 | } 435 | 436 | @Override 437 | public byte[] readBytesTermMulti(byte[] term, boolean includeTerm, boolean consumeTerm, boolean eosError) { 438 | alignToByte(); 439 | int unitSize = term.length; 440 | ByteArrayOutputStream buf = new ByteArrayOutputStream(); 441 | byte[] c = new byte[unitSize]; 442 | while (true) { 443 | int restSize = bb.remaining(); 444 | if (restSize < unitSize) { 445 | if (eosError) { 446 | throw new RuntimeException("End of stream reached, but no terminator " + byteArrayToHex(term) + " found"); 447 | } 448 | bb.get(c, 0, restSize); 449 | buf.write(c, 0, restSize); 450 | return buf.toByteArray(); 451 | } 452 | bb.get(c); 453 | if (Arrays.equals(c, term)) { 454 | if (includeTerm) 455 | buf.write(c, 0, c.length); // see the comment about `buf.write(c)` below 456 | if (!consumeTerm) 457 | bb.position(bb.position() - unitSize); 458 | return buf.toByteArray(); 459 | } 460 | // we could also just use `buf.write(c)`, but then Java thinks that it could throw 461 | // `IOException` when it really can't (Java 11 adds `ByteArrayOutputStream.writeBytes` 462 | // for this reason, but we still want to support Java 7+) 463 | buf.write(c, 0, c.length); 464 | } 465 | } 466 | 467 | //endregion 468 | 469 | @Override 470 | public KaitaiStream substream(long n) { 471 | if (n > Integer.MAX_VALUE) { 472 | throw new IllegalArgumentException("Java ByteBuffer can't be limited beyond Integer.MAX_VALUE"); 473 | } 474 | 475 | ByteBuffer newBuffer = bb.slice(); 476 | newBuffer.limit((int) n); 477 | 478 | bb.position(bb.position() + (int) n); 479 | 480 | return new ByteBufferKaitaiStream(newBuffer); 481 | } 482 | //endregion 483 | 484 | //region Writing 485 | 486 | //region Integer numbers 487 | 488 | //region Signed 489 | 490 | /** 491 | * Writes one signed 1-byte integer. 492 | */ 493 | @Override 494 | public void writeS1(byte v) { 495 | writeAlignToByte(); 496 | bb.put(v); 497 | } 498 | 499 | //region Big-endian 500 | 501 | @Override 502 | public void writeS2be(short v) { 503 | writeAlignToByte(); 504 | bb.order(ByteOrder.BIG_ENDIAN); 505 | bb.putShort(v); 506 | } 507 | 508 | @Override 509 | public void writeS4be(int v) { 510 | writeAlignToByte(); 511 | bb.order(ByteOrder.BIG_ENDIAN); 512 | bb.putInt(v); 513 | } 514 | 515 | @Override 516 | public void writeS8be(long v) { 517 | writeAlignToByte(); 518 | bb.order(ByteOrder.BIG_ENDIAN); 519 | bb.putLong(v); 520 | } 521 | 522 | //endregion 523 | 524 | //region Little-endian 525 | 526 | @Override 527 | public void writeS2le(short v) { 528 | writeAlignToByte(); 529 | bb.order(ByteOrder.LITTLE_ENDIAN); 530 | bb.putShort(v); 531 | } 532 | 533 | @Override 534 | public void writeS4le(int v) { 535 | writeAlignToByte(); 536 | bb.order(ByteOrder.LITTLE_ENDIAN); 537 | bb.putInt(v); 538 | } 539 | 540 | @Override 541 | public void writeS8le(long v) { 542 | writeAlignToByte(); 543 | bb.order(ByteOrder.LITTLE_ENDIAN); 544 | bb.putLong(v); 545 | } 546 | 547 | //endregion 548 | 549 | //endregion 550 | 551 | //endregion 552 | 553 | //region Floating point numbers 554 | 555 | //region Big-endian 556 | 557 | @Override 558 | public void writeF4be(float v) { 559 | writeAlignToByte(); 560 | bb.order(ByteOrder.BIG_ENDIAN); 561 | bb.putFloat(v); 562 | } 563 | 564 | @Override 565 | public void writeF8be(double v) { 566 | writeAlignToByte(); 567 | bb.order(ByteOrder.BIG_ENDIAN); 568 | bb.putDouble(v); 569 | } 570 | 571 | //endregion 572 | 573 | //region Little-endian 574 | 575 | @Override 576 | public void writeF4le(float v) { 577 | writeAlignToByte(); 578 | bb.order(ByteOrder.LITTLE_ENDIAN); 579 | bb.putFloat(v); 580 | } 581 | 582 | @Override 583 | public void writeF8le(double v) { 584 | writeAlignToByte(); 585 | bb.order(ByteOrder.LITTLE_ENDIAN); 586 | bb.putDouble(v); 587 | } 588 | 589 | //endregion 590 | 591 | //endregion 592 | 593 | //region Byte arrays 594 | 595 | @Override 596 | protected void writeBytesNotAligned(byte[] buf) { 597 | bb.put(buf); 598 | } 599 | 600 | //endregion 601 | 602 | //endregion 603 | } 604 | -------------------------------------------------------------------------------- /src/main/java/io/kaitai/struct/ConsistencyError.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015-2025 Kaitai Project: MIT license 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | package io.kaitai.struct; 25 | 26 | public class ConsistencyError extends RuntimeException { 27 | private final String id; 28 | private final Object actual; 29 | private final Object expected; 30 | 31 | public ConsistencyError(String id, Object actual, Object expected) { 32 | super("Check failed: " + id + ", expected: " + expected + ", actual: " + actual); 33 | 34 | this.id = id; 35 | this.actual = actual; 36 | this.expected = expected; 37 | } 38 | 39 | public String id() { return id; } 40 | public Object actual() { return actual; } 41 | public Object expected() { return expected; } 42 | 43 | public static class SizeMismatch extends ConsistencyError { 44 | public SizeMismatch(String id, long actual, long expected) { 45 | super(id, actual, expected); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/io/kaitai/struct/CustomDecoder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015-2025 Kaitai Project: MIT license 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | package io.kaitai.struct; 25 | 26 | /** 27 | * A custom decoder interface. Implementing classes can be called from inside a 28 | * .ksy file using `process: XXX` syntax. 29 | *30 | * This interface is sufficient for custom processing routines that will only be 31 | * used from generated format libraries that are read-only (only capable of 32 | * parsing, not serialization). To support generated source files compiled in 33 | * {@code --read-write} mode, implement {@link CustomProcessor} instead. 34 | */ 35 | public interface CustomDecoder { 36 | /** 37 | * Decodes a given byte array, according to some custom algorithm 38 | * (specific to implementing class) and parameters given in the 39 | * constructor, returning another byte array. 40 | *
41 | * This method is used in parsing. Its counterpart is 42 | * {@link CustomProcessor#encode(byte[])}, which is used in serialization. 43 | * @param src source byte array 44 | * @return decoded byte array 45 | */ 46 | byte[] decode(byte[] src); 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/io/kaitai/struct/CustomProcessor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015-2025 Kaitai Project: MIT license 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | package io.kaitai.struct; 25 | 26 | /** 27 | * A custom encoder/decoder interface. Implementing classes can be called from 28 | * inside a .ksy file using {@code process: XXX} syntax. 29 | *
30 | * Custom processing classes which need to be used from .ksy files that will be 31 | * compiled in {@code --read-write} mode should implement this interface. For 32 | * generated format libraries that are read-only (only capable of parsing, not 33 | * serialization), it's enough to implement {@link CustomDecoder}. 34 | */ 35 | public interface CustomProcessor extends CustomDecoder { 36 | /** 37 | * Encodes a given byte array, according to some custom algorithm (specific 38 | * to implementing class) and parameters given in the constructor, returning 39 | * another byte array. 40 | *
41 | * This method is used in serialization. The inverse operation is
42 | * {@link #decode(byte[])}, which must return the same byte array as
43 | * {@code src} when given the encoded byte array returned by this method.
44 | * @param src source byte array
45 | * @return encoded byte array
46 | */
47 | byte[] encode(byte[] src);
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/io/kaitai/struct/KaitaiStream.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015-2025 Kaitai Project: MIT license
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining
5 | * a copy of this software and associated documentation files (the
6 | * "Software"), to deal in the Software without restriction, including
7 | * without limitation the rights to use, copy, modify, merge, publish,
8 | * distribute, sublicense, and/or sell copies of the Software, and to
9 | * permit persons to whom the Software is furnished to do so, subject to
10 | * the following conditions:
11 | *
12 | * The above copyright notice and this permission notice shall be
13 | * included in all copies or substantial portions of the Software.
14 | *
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | */
23 |
24 | package io.kaitai.struct;
25 |
26 | import java.io.ByteArrayOutputStream;
27 | import java.io.Closeable;
28 | import java.io.EOFException;
29 | import java.io.IOException;
30 | import java.util.ArrayList;
31 | import java.util.Arrays;
32 | import java.util.List;
33 | import java.util.zip.DataFormatException;
34 | import java.util.zip.Inflater;
35 | import java.util.zip.Deflater;
36 |
37 | /**
38 | * KaitaiStream provides implementation of
39 | * Kaitai Stream API
40 | * for Java.
41 | *
42 | * It provides a wide variety of simple methods to read (parse) binary
43 | * representations of primitive types, such as integer and floating
44 | * point numbers, byte arrays and strings, and also provides stream
45 | * positioning / navigation methods with unified cross-language and
46 | * cross-toolkit semantics.
47 | *
48 | * This is abstract class, which serves as an interface description and
49 | * a few default method implementations, which are believed to be common
50 | * for all (or at least most) implementations. Different implementations
51 | * of this interface may provide way to parse data from local files,
52 | * in-memory buffers or arrays, remote files, network streams, etc.
53 | *
54 | * Typically, end users won't access any of these Kaitai Stream classes
55 | * manually, but would describe a binary structure format using .ksy language
56 | * and then would use Kaitai Struct compiler to generate source code in
57 | * desired target language. That code, in turn, would use this class
58 | * and API to do the actual parsing job.
59 | */
60 | public abstract class KaitaiStream implements Closeable {
61 | protected int bitsLeft = 0;
62 | protected long bits = 0;
63 | protected boolean bitsLe = false;
64 | protected boolean bitsWriteMode = false;
65 |
66 | protected WriteBackHandler writeBackHandler;
67 |
68 | protected List
40 | *
46 | */
47 | public class RandomAccessFileKaitaiStream extends KaitaiStream {
48 | protected RandomAccessFile raf;
49 |
50 | public RandomAccessFileKaitaiStream(String fileName) throws IOException {
51 | raf = new RandomAccessFile(fileName, "r");
52 | }
53 |
54 | public RandomAccessFileKaitaiStream(RandomAccessFile raf) {
55 | this.raf = raf;
56 | }
57 |
58 | @Override
59 | public void close() throws IOException {
60 | Exception exc = null;
61 | try {
62 | if (bitsWriteMode) {
63 | writeAlignToByte();
64 | } else {
65 | alignToByte();
66 | }
67 | } catch (Exception e) {
68 | exc = e;
69 | throw e;
70 | } finally {
71 | try {
72 | raf.close();
73 | } catch (IOException e) {
74 | if (exc != null) {
75 | // deliver RandomAccessFile.close() exception as primary, the one from
76 | // writeAlignToByte() as suppressed
77 | e.addSuppressed(exc);
78 | }
79 | throw e;
80 | }
81 | }
82 | }
83 |
84 | //region Stream positioning
85 |
86 | @Override
87 | public boolean isEof() {
88 | try {
89 | return !(raf.getFilePointer() < raf.length() || (!bitsWriteMode && bitsLeft > 0));
90 | } catch (IOException e) {
91 | throw new RuntimeException(e);
92 | }
93 | }
94 |
95 | @Override
96 | public void seek(int newPos) {
97 | seek((long) newPos);
98 | }
99 |
100 | @Override
101 | public void seek(long newPos) {
102 | if (bitsWriteMode) {
103 | writeAlignToByte();
104 | } else {
105 | alignToByte();
106 | }
107 | try {
108 | raf.seek(newPos);
109 | } catch (IOException e) {
110 | throw new RuntimeException(e);
111 | }
112 | }
113 |
114 | @Override
115 | public int pos() {
116 | try {
117 | // FIXME cast
118 | return (int) raf.getFilePointer() + ((bitsWriteMode && bitsLeft > 0) ? 1 : 0);
119 | } catch (IOException e) {
120 | throw new RuntimeException(e);
121 | }
122 | }
123 |
124 | @Override
125 | public long size() {
126 | try {
127 | return raf.length();
128 | } catch (IOException e) {
129 | throw new RuntimeException(e);
130 | }
131 | }
132 |
133 | //endregion
134 |
135 | //region Reading
136 |
137 | //region Integer numbers
138 |
139 | //region Signed
140 |
141 | @Override
142 | public byte readS1() {
143 | alignToByte();
144 | try {
145 | int t = raf.read();
146 | if (t < 0) {
147 | throw new EOFException();
148 | } else {
149 | return (byte) t;
150 | }
151 | } catch (IOException e) {
152 | throw new RuntimeException(e);
153 | }
154 | }
155 |
156 | //region Big-endian
157 |
158 | @Override
159 | public short readS2be() {
160 | alignToByte();
161 | try {
162 | int b1 = raf.read();
163 | int b2 = raf.read();
164 | if ((b1 | b2) < 0) {
165 | throw new EOFException();
166 | } else {
167 | return (short) ((b1 << 8) + (b2 << 0));
168 | }
169 | } catch (IOException e) {
170 | throw new RuntimeException(e);
171 | }
172 | }
173 |
174 | @Override
175 | public int readS4be() {
176 | alignToByte();
177 | try {
178 | int b1 = raf.read();
179 | int b2 = raf.read();
180 | int b3 = raf.read();
181 | int b4 = raf.read();
182 | if ((b1 | b2 | b3 | b4) < 0) {
183 | throw new EOFException();
184 | } else {
185 | return (b1 << 24) + (b2 << 16) + (b3 << 8) + (b4 << 0);
186 | }
187 | } catch (IOException e) {
188 | throw new RuntimeException(e);
189 | }
190 | }
191 |
192 | @Override
193 | public long readS8be() {
194 | alignToByte();
195 | long b1 = readU4be();
196 | long b2 = readU4be();
197 | return (b1 << 32) + (b2 << 0);
198 | }
199 |
200 | //endregion
201 |
202 | //region Little-endian
203 |
204 | @Override
205 | public short readS2le() {
206 | alignToByte();
207 | try {
208 | int b1 = raf.read();
209 | int b2 = raf.read();
210 | if ((b1 | b2) < 0) {
211 | throw new EOFException();
212 | } else {
213 | return (short) ((b2 << 8) + (b1 << 0));
214 | }
215 | } catch (IOException e) {
216 | throw new RuntimeException(e);
217 | }
218 | }
219 |
220 | @Override
221 | public int readS4le() {
222 | alignToByte();
223 | try {
224 | int b1 = raf.read();
225 | int b2 = raf.read();
226 | int b3 = raf.read();
227 | int b4 = raf.read();
228 | if ((b1 | b2 | b3 | b4) < 0) {
229 | throw new EOFException();
230 | } else {
231 | return (b4 << 24) + (b3 << 16) + (b2 << 8) + (b1 << 0);
232 | }
233 | } catch (IOException e) {
234 | throw new RuntimeException(e);
235 | }
236 | }
237 |
238 | @Override
239 | public long readS8le() {
240 | alignToByte();
241 | long b1 = readU4le();
242 | long b2 = readU4le();
243 | return (b2 << 32) + (b1 << 0);
244 | }
245 |
246 | //endregion
247 |
248 | //endregion
249 |
250 | //region Unsigned
251 |
252 | @Override
253 | public int readU1() {
254 | alignToByte();
255 | try {
256 | int t = raf.read();
257 | if (t < 0) {
258 | throw new EOFException();
259 | } else {
260 | return t;
261 | }
262 | } catch (IOException e) {
263 | throw new RuntimeException(e);
264 | }
265 | }
266 |
267 | //region Big-endian
268 |
269 | @Override
270 | public int readU2be() {
271 | alignToByte();
272 | try {
273 | int b1 = raf.read();
274 | int b2 = raf.read();
275 | if ((b1 | b2) < 0) {
276 | throw new EOFException();
277 | } else {
278 | return (b1 << 8) + (b2 << 0);
279 | }
280 | } catch (IOException e) {
281 | throw new RuntimeException(e);
282 | }
283 | }
284 |
285 | @Override
286 | public long readU4be() {
287 | alignToByte();
288 | try {
289 | long b1 = raf.read();
290 | long b2 = raf.read();
291 | long b3 = raf.read();
292 | long b4 = raf.read();
293 | if ((b1 | b2 | b3 | b4) < 0) {
294 | throw new EOFException();
295 | } else {
296 | return (b1 << 24) + (b2 << 16) + (b3 << 8) + (b4 << 0);
297 | }
298 | } catch (IOException e) {
299 | throw new RuntimeException(e);
300 | }
301 | }
302 |
303 | //endregion
304 |
305 | //region Little-endian
306 |
307 | @Override
308 | public int readU2le() {
309 | alignToByte();
310 | try {
311 | int b1 = raf.read();
312 | int b2 = raf.read();
313 | if ((b1 | b2) < 0) {
314 | throw new EOFException();
315 | } else {
316 | return (b2 << 8) + (b1 << 0);
317 | }
318 | } catch (IOException e) {
319 | throw new RuntimeException(e);
320 | }
321 | }
322 |
323 | @Override
324 | public long readU4le() {
325 | alignToByte();
326 | try {
327 | long b1 = raf.read();
328 | long b2 = raf.read();
329 | long b3 = raf.read();
330 | long b4 = raf.read();
331 | if ((b1 | b2 | b3 | b4) < 0) {
332 | throw new EOFException();
333 | } else {
334 | return (b4 << 24) + (b3 << 16) + (b2 << 8) + (b1 << 0);
335 | }
336 | } catch (IOException e) {
337 | throw new RuntimeException(e);
338 | }
339 | }
340 |
341 | //endregion
342 |
343 | //endregion
344 |
345 | //endregion
346 |
347 | //region Floating point numbers
348 |
349 | //region Big-endian
350 |
351 | @Override
352 | public float readF4be() {
353 | return Float.intBitsToFloat(readS4be());
354 | }
355 |
356 | @Override
357 | public double readF8be() {
358 | return Double.longBitsToDouble(readS8be());
359 | }
360 |
361 | //endregion
362 |
363 | //region Little-endian
364 |
365 | @Override
366 | public float readF4le() {
367 | return Float.intBitsToFloat(readS4le());
368 | }
369 |
370 | @Override
371 | public double readF8le() {
372 | return Double.longBitsToDouble(readS8le());
373 | }
374 |
375 | //endregion
376 |
377 | //endregion
378 |
379 | //region Byte arrays
380 |
381 | @Override
382 | protected byte[] readBytesNotAligned(long n) {
383 | byte[] buf = new byte[toByteArrayLength(n)];
384 | try {
385 | raf.readFully(buf);
386 | return buf;
387 | } catch (IOException e) {
388 | throw new RuntimeException(e);
389 | }
390 | }
391 |
392 | private static final int DEFAULT_BUFFER_SIZE = 4 * 1024;
393 |
394 | @Override
395 | public byte[] readBytesFull() {
396 | alignToByte();
397 | byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
398 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
399 |
400 | int readCount;
401 | try {
402 | while (-1 != (readCount = raf.read(buffer)))
403 | baos.write(buffer, 0, readCount);
404 |
405 | return baos.toByteArray();
406 | } catch (IOException e) {
407 | throw new RuntimeException(e);
408 | }
409 | }
410 |
411 | @Override
412 | public byte[] readBytesTerm(byte term, boolean includeTerm, boolean consumeTerm, boolean eosError) {
413 | alignToByte();
414 | ByteArrayOutputStream buf = new ByteArrayOutputStream();
415 | try {
416 | while (true) {
417 | int c = raf.read();
418 | if (c < 0) {
419 | if (eosError) {
420 | throw new RuntimeException("End of stream reached, but no terminator " + term + " found");
421 | }
422 | return buf.toByteArray();
423 | }
424 | if ((byte) c == term) {
425 | if (includeTerm)
426 | buf.write(c);
427 | if (!consumeTerm)
428 | raf.seek(raf.getFilePointer() - 1);
429 | return buf.toByteArray();
430 | }
431 | buf.write(c);
432 | }
433 | } catch (IOException e) {
434 | throw new RuntimeException(e);
435 | }
436 | }
437 |
438 | @Override
439 | public byte[] readBytesTermMulti(byte[] term, boolean includeTerm, boolean consumeTerm, boolean eosError) {
440 | alignToByte();
441 | int unitSize = term.length;
442 | ByteArrayOutputStream buf = new ByteArrayOutputStream();
443 | byte[] c = new byte[unitSize];
444 | try {
445 | int readCount = 0;
446 | while (true) {
447 | // Inspired by the [Java implementation of
448 | // `RandomAccessFile.readFully`](https://github.com/openjdk/jdk/blob/2f2223d7524c/src/java.base/share/classes/java/io/RandomAccessFile.java#L539-L547)
449 | // (we want something like readFully() here in the sense that we'd like to read all
450 | // `unitSize` bytes if available, but we don't want to throw an EOF exception)
451 | int count = raf.read(c, readCount, unitSize - readCount);
452 | if (count >= 0) {
453 | readCount += count;
454 | if (readCount < unitSize) {
455 | // we have received some bytes, but we need `unitSize` bytes, so we'll
456 | // continue reading
457 | continue;
458 | }
459 | } else {
460 | if (eosError) {
461 | throw new RuntimeException("End of stream reached, but no terminator " + byteArrayToHex(term) + " found");
462 | }
463 | buf.write(c, 0, readCount);
464 | return buf.toByteArray();
465 | }
466 | if (Arrays.equals(c, term)) {
467 | if (includeTerm)
468 | buf.write(c, 0, c.length); // see the comment about `buf.write(c)` in `ByteBufferKaitaiStream.readBytesTermMulti`
469 | if (!consumeTerm)
470 | raf.seek(raf.getFilePointer() - unitSize);
471 | return buf.toByteArray();
472 | }
473 | buf.write(c, 0, c.length); // see the comment about `buf.write(c)` in `ByteBufferKaitaiStream.readBytesTermMulti`
474 | readCount = 0;
475 | }
476 | } catch (IOException e) {
477 | throw new RuntimeException(e);
478 | }
479 | }
480 |
481 | @Override
482 | public KaitaiStream substream(long n) {
483 | // This implementation mirrors what ksc was doing up to v0.10, and the fallback that
484 | // it is still doing in case something non-trivial has to happen with the byte contents.
485 | //
486 | // Given that RandomAccessFile-based stream is not really efficient anyway, this seems
487 | // to be a reasonable fallback without resorting to a special limiting implementation.
488 | //
489 | // If and when somebody will come up with a reason why substreams have to implemented
490 | // for RAF, feel free to contribute relevant implementation with some rationale (e.g. a
491 | // benchmark).
492 |
493 | return new ByteBufferKaitaiStream(readBytes(n));
494 | }
495 |
496 | //region Helper methods
497 | //endregion
498 |
499 | //endregion
500 |
501 | //region Writing
502 |
503 | protected void ensureBytesLeftToWrite(long n) throws IOException {
504 | ensureBytesLeftToWrite(n, raf.getFilePointer());
505 | }
506 |
507 | //region Integer numbers
508 |
509 | //region Signed
510 |
511 | /**
512 | * Writes one signed 1-byte integer.
513 | */
514 | @Override
515 | public void writeS1(byte v) {
516 | writeAlignToByte();
517 | try {
518 | ensureBytesLeftToWrite(1);
519 | raf.write(v);
520 | } catch (IOException e) {
521 | throw new RuntimeException(e);
522 | }
523 | }
524 |
525 | //region Big-endian
526 |
527 | @Override
528 | public void writeS2be(short v) {
529 | writeAlignToByte();
530 | try {
531 | ensureBytesLeftToWrite(2);
532 | raf.write((v >>> 8) & 0xFF);
533 | raf.write((v >>> 0) & 0xFF);
534 | } catch (IOException e) {
535 | throw new RuntimeException(e);
536 | }
537 | }
538 |
539 | @Override
540 | public void writeS4be(int v) {
541 | writeAlignToByte();
542 | try {
543 | ensureBytesLeftToWrite(4);
544 | raf.write((v >>> 24) & 0xFF);
545 | raf.write((v >>> 16) & 0xFF);
546 | raf.write((v >>> 8) & 0xFF);
547 | raf.write((v >>> 0) & 0xFF);
548 | } catch (IOException e) {
549 | throw new RuntimeException(e);
550 | }
551 | }
552 |
553 | @Override
554 | public void writeS8be(long v) {
555 | writeAlignToByte();
556 | try {
557 | ensureBytesLeftToWrite(8);
558 | raf.write((int)(v >>> 56) & 0xFF);
559 | raf.write((int)(v >>> 48) & 0xFF);
560 | raf.write((int)(v >>> 40) & 0xFF);
561 | raf.write((int)(v >>> 32) & 0xFF);
562 | raf.write((int)(v >>> 24) & 0xFF);
563 | raf.write((int)(v >>> 16) & 0xFF);
564 | raf.write((int)(v >>> 8) & 0xFF);
565 | raf.write((int)(v >>> 0) & 0xFF);
566 | } catch (IOException e) {
567 | throw new RuntimeException(e);
568 | }
569 | }
570 |
571 | //endregion
572 |
573 | //region Little-endian
574 |
575 | @Override
576 | public void writeS2le(short v) {
577 | writeAlignToByte();
578 | try {
579 | ensureBytesLeftToWrite(2);
580 | raf.write((v >>> 0) & 0xFF);
581 | raf.write((v >>> 8) & 0xFF);
582 | } catch (IOException e) {
583 | throw new RuntimeException(e);
584 | }
585 | }
586 |
587 | @Override
588 | public void writeS4le(int v) {
589 | writeAlignToByte();
590 | try {
591 | ensureBytesLeftToWrite(4);
592 | raf.write((v >>> 0) & 0xFF);
593 | raf.write((v >>> 8) & 0xFF);
594 | raf.write((v >>> 16) & 0xFF);
595 | raf.write((v >>> 24) & 0xFF);
596 | } catch (IOException e) {
597 | throw new RuntimeException(e);
598 | }
599 | }
600 |
601 | @Override
602 | public void writeS8le(long v) {
603 | writeAlignToByte();
604 | try {
605 | ensureBytesLeftToWrite(8);
606 | raf.write((int)(v >>> 0) & 0xFF);
607 | raf.write((int)(v >>> 8) & 0xFF);
608 | raf.write((int)(v >>> 16) & 0xFF);
609 | raf.write((int)(v >>> 24) & 0xFF);
610 | raf.write((int)(v >>> 32) & 0xFF);
611 | raf.write((int)(v >>> 40) & 0xFF);
612 | raf.write((int)(v >>> 48) & 0xFF);
613 | raf.write((int)(v >>> 56) & 0xFF);
614 | } catch (IOException e) {
615 | throw new RuntimeException(e);
616 | }
617 | }
618 |
619 | //endregion
620 |
621 | //endregion
622 |
623 | //endregion
624 |
625 | //region Floating point numbers
626 |
627 | //region Big-endian
628 |
629 | @Override
630 | public void writeF4be(float v) {
631 | writeS4be(Float.floatToIntBits(v));
632 | }
633 |
634 | @Override
635 | public void writeF8be(double v) {
636 | writeS8be(Double.doubleToLongBits(v));
637 | }
638 |
639 | //endregion
640 |
641 | //region Little-endian
642 |
643 | @Override
644 | public void writeF4le(float v) {
645 | writeS4le(Float.floatToIntBits(v));
646 | }
647 |
648 | @Override
649 | public void writeF8le(double v) {
650 | writeS8le(Double.doubleToLongBits(v));
651 | }
652 |
653 | //endregion
654 |
655 | //endregion
656 |
657 | //region Byte arrays
658 |
659 | @Override
660 | protected void writeBytesNotAligned(byte[] buf) {
661 | try {
662 | ensureBytesLeftToWrite(buf.length);
663 | raf.write(buf);
664 | } catch (IOException e) {
665 | throw new RuntimeException(e);
666 | }
667 | }
668 |
669 | //endregion
670 |
671 | //endregion
672 | }
673 |
--------------------------------------------------------------------------------
/src/main/java/io/kaitai/struct/annotations/Generated.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015-2025 Kaitai Project: MIT license
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining
5 | * a copy of this software and associated documentation files (the
6 | * "Software"), to deal in the Software without restriction, including
7 | * without limitation the rights to use, copy, modify, merge, publish,
8 | * distribute, sublicense, and/or sell copies of the Software, and to
9 | * permit persons to whom the Software is furnished to do so, subject to
10 | * the following conditions:
11 | *
12 | * The above copyright notice and this permission notice shall be
13 | * included in all copies or substantial portions of the Software.
14 | *
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | */
23 | package io.kaitai.struct.annotations;
24 |
25 | import java.lang.annotation.Documented;
26 | import java.lang.annotation.ElementType;
27 | import java.lang.annotation.Retention;
28 | import java.lang.annotation.RetentionPolicy;
29 | import java.lang.annotation.Target;
30 |
31 | /**
32 | * Annotation, that applied to Kaitai-generated classes. Visualizers can use that
33 | * annotation to find classes, that contains generated stuff, that should be showed
34 | * in visualization.
35 | *
36 | * @since 0.9
37 | */
38 | @Documented
39 | @Retention(RetentionPolicy.RUNTIME)
40 | @Target(ElementType.TYPE)
41 | public @interface Generated {
42 | /**
43 | * Original identifier ({@code id} key) from {@code ksy} file.
44 | *
45 | * @return Identifier, that can differ from class name, if it clash with
46 | * Java reserved words. Can not be empty
47 | */
48 | String id();
49 | /**
50 | * Version of compiler, that generated this class.
51 | *
52 | * @return Version string in semver format
53 | */
54 | String version();
55 | /**
56 | * Class compiled with support of position tracking. That means, that every class
57 | * has following public fields (in that version of generator):
58 | *
59 | *
82 | *
83 | * @return {@code true}, if position tracking is enabled and {@code false} otherwise
84 | */
85 | boolean posInfo();
86 | /**
87 | * Determines, if instantiation of user classes (related to user-types, defined
88 | * in {@code ksy} file) automatically read its content from the stream, or that must
89 | * be performed manually by calling generated {@code _read()}, {@code _readBE()}
90 | * or {@code _readLE()} method.
91 | *
92 | * @return {@code true}, if generated {@code _read()} method invoked automatically
93 | * by class constructors and {@code false}, if it must be called explicitly
94 | */
95 | boolean autoRead();
96 | /**
97 | * Documentation string attached to the type definition, specified in {@code doc}
98 | * KSY element.
99 | *
100 | * @return Documentation string for a type. If documentation is missed, returns empty string
101 | */
102 | String doc();
103 | }
104 |
--------------------------------------------------------------------------------
/src/main/java/io/kaitai/struct/annotations/Instance.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015-2025 Kaitai Project: MIT license
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining
5 | * a copy of this software and associated documentation files (the
6 | * "Software"), to deal in the Software without restriction, including
7 | * without limitation the rights to use, copy, modify, merge, publish,
8 | * distribute, sublicense, and/or sell copies of the Software, and to
9 | * permit persons to whom the Software is furnished to do so, subject to
10 | * the following conditions:
11 | *
12 | * The above copyright notice and this permission notice shall be
13 | * included in all copies or substantial portions of the Software.
14 | *
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | */
23 | package io.kaitai.struct.annotations;
24 |
25 | import java.lang.annotation.Documented;
26 | import java.lang.annotation.ElementType;
27 | import java.lang.annotation.Retention;
28 | import java.lang.annotation.RetentionPolicy;
29 | import java.lang.annotation.Target;
30 |
31 | /**
32 | * Annotation, that applied to fields, getters or setters that represents instance
33 | * field from {@code instances} KSY element.
34 | *
35 | * @since 0.9
36 | */
37 | @Documented
38 | @Retention(RetentionPolicy.RUNTIME)
39 | @Target({ElementType.FIELD, ElementType.METHOD})
40 | public @interface Instance {
41 | /**
42 | * Original identifier ({@code id} key) from {@code ksy} file.
43 | *
44 | * @return Identifier, that can differ from instance name, if it clash with
45 | * Java reserved words. Can not be empty
46 | */
47 | String id();
48 | /**
49 | * Documentation string attached to the instance definition, specified in {@code doc}
50 | * KSY element.
51 | *
52 | * @return Documentation string for an instance. If documentation is missed,
53 | * returns empty string
54 | */
55 | String doc();
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/io/kaitai/struct/annotations/Parameter.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015-2025 Kaitai Project: MIT license
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining
5 | * a copy of this software and associated documentation files (the
6 | * "Software"), to deal in the Software without restriction, including
7 | * without limitation the rights to use, copy, modify, merge, publish,
8 | * distribute, sublicense, and/or sell copies of the Software, and to
9 | * permit persons to whom the Software is furnished to do so, subject to
10 | * the following conditions:
11 | *
12 | * The above copyright notice and this permission notice shall be
13 | * included in all copies or substantial portions of the Software.
14 | *
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | */
23 | package io.kaitai.struct.annotations;
24 |
25 | import java.lang.annotation.Documented;
26 | import java.lang.annotation.ElementType;
27 | import java.lang.annotation.Retention;
28 | import java.lang.annotation.RetentionPolicy;
29 | import java.lang.annotation.Target;
30 |
31 | /**
32 | * Annotation, that applied to fields, getters or setters that represents parameter
33 | * from {@code params} KSY element.
34 | *
35 | * @since 0.9
36 | */
37 | @Documented
38 | @Retention(RetentionPolicy.RUNTIME)
39 | @Target({ElementType.FIELD, ElementType.METHOD})
40 | public @interface Parameter {
41 | /**
42 | * Original identifier ({@code id} key) from {@code ksy} file.
43 | *
44 | * @return Identifier, that can differ from parameter name, if it clash with
45 | * Java reserved words. Can not be empty
46 | */
47 | String id();
48 | /**
49 | * Index of a parameter in sequence of parameters in the type.
50 | *
51 | * @return 0-based index of a parameter in {@code params} KSY element
52 | */
53 | int index();
54 | /**
55 | * Documentation string attached to the parameter, specified in {@code doc}
56 | * KSY element.
57 | *
58 | * @return Documentation string for parameter. If documentation is missed,
59 | * returns empty string
60 | */
61 | String doc();
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/io/kaitai/struct/annotations/SeqItem.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015-2025 Kaitai Project: MIT license
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining
5 | * a copy of this software and associated documentation files (the
6 | * "Software"), to deal in the Software without restriction, including
7 | * without limitation the rights to use, copy, modify, merge, publish,
8 | * distribute, sublicense, and/or sell copies of the Software, and to
9 | * permit persons to whom the Software is furnished to do so, subject to
10 | * the following conditions:
11 | *
12 | * The above copyright notice and this permission notice shall be
13 | * included in all copies or substantial portions of the Software.
14 | *
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | */
23 | package io.kaitai.struct.annotations;
24 |
25 | import java.lang.annotation.Documented;
26 | import java.lang.annotation.ElementType;
27 | import java.lang.annotation.Retention;
28 | import java.lang.annotation.RetentionPolicy;
29 | import java.lang.annotation.Target;
30 |
31 | /**
32 | * Annotation, that applied to fields, getters or setters that represents an attribute
33 | * from {@code seq} KSY element.
34 | *
35 | * @since 0.9
36 | */
37 | @Documented
38 | @Retention(RetentionPolicy.RUNTIME)
39 | @Target({ElementType.FIELD, ElementType.METHOD})
40 | public @interface SeqItem {
41 | /**
42 | * Original identifier ({@code id} key) from {@code ksy} file.
43 | *
44 | * @return Identifier, that can differ from field name, if it clash with
45 | * Java reserved words. Empty string, if attribute was unnamed
46 | */
47 | String id();
48 | /**
49 | * Index of an attribute in sequence of attributes in the type.
50 | *
51 | * @return 0-based index of an attribute in {@code seq} KSY element
52 | */
53 | int index();
54 | /**
55 | * Documentation string attached to the attribute, specified in {@code doc}
56 | * KSY element.
57 | *
58 | * @return Documentation string for and attribute. If documentation is missed,
59 | * returns empty string
60 | */
61 | String doc();
62 | }
63 |
--------------------------------------------------------------------------------
/src/test/java/io/kaitai/struct/ByteBufferKaitaiStreamTest.java:
--------------------------------------------------------------------------------
1 | package io.kaitai.struct;
2 |
3 | import java.nio.BufferUnderflowException;
4 |
5 | public class ByteBufferKaitaiStreamTest extends KaitaiStreamTest {
6 | @org.testng.annotations.BeforeMethod
7 | public void setUp() {
8 | stream = new ByteBufferKaitaiStream(new byte[] { '1', '2', '3', '4', '5' });
9 | }
10 |
11 | @Override
12 | Class getEOFClass() {
13 | return BufferUnderflowException.class;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/test/java/io/kaitai/struct/KaitaiStreamTest.java:
--------------------------------------------------------------------------------
1 | package io.kaitai.struct;
2 |
3 | import org.testng.annotations.Test;
4 |
5 | import java.nio.BufferUnderflowException;
6 |
7 | import static org.testng.Assert.*;
8 |
9 | public abstract class KaitaiStreamTest {
10 | KaitaiStream stream;
11 |
12 | abstract Class getEOFClass();
13 |
14 | @org.testng.annotations.Test
15 | public void testReadS1() {
16 | short first = stream.readS1();
17 | assertEquals(first, 0x31);
18 | short second = stream.readS1();
19 | assertEquals(second, 0x32);
20 | }
21 |
22 | @org.testng.annotations.Test
23 | public void testReadS2be() {
24 | short first = stream.readS2be();
25 | assertEquals(first, 0x3132);
26 | short second = stream.readS2be();
27 | assertEquals(second, 0x3334);
28 |
29 | assertThrows(getEOFClass(), new ThrowingRunnable() {
30 | @Override
31 | public void run() throws Throwable {
32 | stream.readS2be();
33 | }
34 | });
35 | }
36 |
37 | @org.testng.annotations.Test
38 | public void testReadBytes5() {
39 | byte[] actual = stream.readBytes(5);
40 | assertEquals(actual, new byte[] {'1', '2', '3', '4', '5'});
41 | }
42 |
43 | @org.testng.annotations.Test
44 | public void testReadBytes6() {
45 | assertThrows(getEOFClass(), new ThrowingRunnable() {
46 | @Override
47 | public void run() throws Throwable {
48 | stream.readBytes(6);
49 | }
50 | });
51 | }
52 |
53 | @org.testng.annotations.Test
54 | public void testSubstream() {
55 | stream.seek(1);
56 | assertEquals(stream.pos(), 1);
57 |
58 | final KaitaiStream substream = stream.substream(3);
59 |
60 | assertEquals(substream.pos(), 0);
61 | assertEquals(stream.pos(), 4);
62 |
63 | byte byte0Sub = substream.readS1();
64 | assertEquals(byte0Sub, '2');
65 | assertEquals(substream.pos(), 1);
66 | assertEquals(stream.pos(), 4);
67 |
68 | byte byte1Sub = substream.readS1();
69 | assertEquals(byte1Sub, '3');
70 | assertEquals(substream.pos(), 2);
71 | assertEquals(stream.pos(), 4);
72 |
73 | byte byte4Main = stream.readS1();
74 | assertEquals(byte4Main, '5');
75 | assertEquals(substream.pos(), 2);
76 | assertEquals(stream.pos(), 5);
77 |
78 | byte byte2Sub = substream.readS1();
79 | assertEquals(byte2Sub, '4');
80 | assertEquals(substream.pos(), 3);
81 | assertEquals(stream.pos(), 5);
82 |
83 | assertThrows(getEOFClass(), new ThrowingRunnable() {
84 | @Override
85 | public void run() throws Throwable {
86 | substream.readS1();
87 | }
88 | });
89 |
90 | assertTrue(substream.isEof());
91 | }
92 | }
--------------------------------------------------------------------------------
/src/test/java/io/kaitai/struct/RandomAccessFileKaitaiStreamTest.java:
--------------------------------------------------------------------------------
1 | package io.kaitai.struct;
2 |
3 | import java.io.EOFException;
4 | import java.io.File;
5 | import java.io.FileWriter;
6 | import java.io.IOException;
7 | import java.nio.BufferUnderflowException;
8 |
9 | import static org.testng.Assert.*;
10 |
11 | public class RandomAccessFileKaitaiStreamTest extends KaitaiStreamTest {
12 | static String TEST_SCRATCH_DIR = "target/test-scratch";
13 |
14 | @org.testng.annotations.BeforeMethod
15 | public void setUp() throws IOException {
16 | File testScratchDir = new File(TEST_SCRATCH_DIR);
17 | if (!testScratchDir.exists()) testScratchDir.mkdirs();
18 |
19 | String testFileName = testScratchDir + "/12345.bin";
20 |
21 | FileWriter writer = new FileWriter(testFileName);
22 | writer.write("12345");
23 | writer.close();
24 |
25 | stream = new RandomAccessFileKaitaiStream(testFileName);
26 | }
27 |
28 | @Override
29 | Class getEOFClass() {
30 | return RuntimeException.class;
31 | }
32 | }
--------------------------------------------------------------------------------
61 | * Type Field Description
66 | * {@code Map {@code _attrStart}
62 | * Start offset in the root stream, where {@link SeqItem an attribute} or
63 | * {@link Instance an instance} with specified name begins.
64 | * Used only for attributes/instances, that is not repeated
65 | *
71 | * {@code Map {@code _attrEnd}
67 | * Start offset in the root stream, where {@link SeqItem an attribute} or
68 | * {@link Instance an instance} with specified name ends (exclusive).
69 | * Used only for attributes/instances, that is not repeated
70 | *
76 | * {@code Map {@code _arrStart}
72 | * List with start offset in the root stream, where each array element of
73 | * repeated {@link SeqItem attribute} or {@link Instance instance} with
74 | * specified name begins. Used only for attributes/instances, that is repeated
75 | *
81 | * {@code Map {@code _arrEnd}
77 | * List with end offset (exclusive) in the root stream, where each array
78 | * element of repeated {@link SeqItem attribute} or {@link Instance instance}
79 | * with specified name ends. Used only for attributes/instances, that is repeated
80 | *