├── .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 | [![Maven Central](https://img.shields.io/maven-central/v/io.kaitai/kaitai-struct-runtime)](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 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | io.kaitai 5 | kaitai-struct-runtime 6 | jar 7 | 0.11-SNAPSHOT 8 | 9 | Kaitai Struct runtime library for Java 10 | 11 | Kaitai Struct is a declarative language used for describe various 12 | binary data structures using .ksy format. .ksy format can be compiled 13 | into the parser source code in target language. This library is a 14 | small collection of runtime methods used by the code generated by 15 | Kaitai Struct for Java. 16 | 17 | https://kaitai.io 18 | 19 | 20 | 21 | MIT License 22 | https://opensource.org/licenses/MIT 23 | 24 | 25 | 26 | 27 | 28 | Mikhail Yakshin 29 | greycat.na.kor@gmail.com 30 | Kaitai Project 31 | https://kaitai.io 32 | 33 | 34 | 35 | 36 | scm:git:git://github.com/kaitai-io/kaitai_struct_java_runtime.git 37 | scm:git:ssh://github.com:kaitai-io/kaitai_struct_java_runtime.git 38 | https://github.com/kaitai-io/kaitai_struct_java_runtime 39 | 40 | 41 | 42 | UTF-8 43 | 1.7 44 | 1.7 45 | 46 | 47 | 48 | 49 | jdk9 50 | 51 | [1.9,) 52 | 53 | 54 | 7 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | org.apache.maven.plugins 63 | maven-compiler-plugin 64 | 3.10.1 65 | 66 | 67 | org.apache.maven.plugins 68 | maven-source-plugin 69 | 2.2.1 70 | 71 | 72 | attach-sources 73 | 74 | jar-no-fork 75 | 76 | 77 | 78 | 79 | 80 | org.apache.maven.plugins 81 | maven-javadoc-plugin 82 | 3.2.0 83 | 84 | 85 | attach-javadocs 86 | 87 | jar 88 | 89 | 90 | 91 | 92 | 93 | 94 | apiNote 95 | a 96 | API Note: 97 | 98 | 99 | implSpec 100 | a 101 | Implementation Requirements: 102 | 103 | 104 | implNote 105 | a 106 | Implementation Note: 107 | 108 | 109 | param 110 | return 111 | throws 112 | since 113 | version 114 | serialData 115 | see 116 | 117 | 118 | 119 | 120 | org.apache.maven.plugins 121 | maven-gpg-plugin 122 | 1.5 123 | 124 | 125 | sign-artifacts 126 | verify 127 | 128 | sign 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | ossrh 139 | https://oss.sonatype.org/content/repositories/snapshots 140 | 141 | 142 | ossrh 143 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 144 | 145 | 146 | 147 | 148 | 149 | 150 | org.testng 151 | testng 152 | 7.8.0 153 | test 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /src/main/java/io/kaitai/struct/ByteBufferKaitaiStream.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.IOException; 28 | import java.nio.ByteBuffer; 29 | import java.nio.ByteOrder; 30 | import java.nio.MappedByteBuffer; 31 | import java.nio.channels.FileChannel; 32 | import java.nio.file.Paths; 33 | import java.nio.file.StandardOpenOption; 34 | import java.util.Arrays; 35 | 36 | /** 37 | * An implementation of {@link KaitaiStream} backed by a {@link ByteBuffer}. 38 | * Any underlying implementation of {@link ByteBuffer} can be used, for example: 39 | * 44 | */ 45 | public class ByteBufferKaitaiStream extends KaitaiStream { 46 | private FileChannel fc; 47 | private ByteBuffer bb; 48 | 49 | /** 50 | * Initializes a stream, reading from a local file with specified {@code fileName}. 51 | * Internally, {@link FileChannel} + {@link MappedByteBuffer} will be used. 52 | * @param fileName file to read 53 | * @throws IOException if file can't be read 54 | */ 55 | public ByteBufferKaitaiStream(String fileName) throws IOException { 56 | fc = FileChannel.open(Paths.get(fileName), StandardOpenOption.READ); 57 | bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); 58 | } 59 | 60 | /** 61 | * Initializes a stream that will get data from the given array on read and put data 62 | * into the array on write. Internally, a {@link ByteBuffer} is used to wrap the given 63 | * array. 64 | * @param arr byte array to read from or write to 65 | */ 66 | public ByteBufferKaitaiStream(byte[] arr) { 67 | fc = null; 68 | bb = ByteBuffer.wrap(arr); 69 | } 70 | 71 | /** 72 | * Initializes a stream that will get data from given {@link ByteBuffer} on read and 73 | * put data into it on write. 74 | * @param buffer {@link ByteBuffer} to read from or write to 75 | */ 76 | public ByteBufferKaitaiStream(ByteBuffer buffer) { 77 | fc = null; 78 | bb = buffer; 79 | } 80 | 81 | /** 82 | * Initializes a stream that will write data into a fixed byte buffer in memory. 83 | * @param size size of buffer in bytes 84 | */ 85 | public ByteBufferKaitaiStream(long size) { 86 | if (size > Integer.MAX_VALUE) { 87 | throw new RuntimeException("Java ByteBuffer can't be longer than Integer.MAX_VALUE"); 88 | } 89 | fc = null; 90 | bb = ByteBuffer.allocate((int) size); 91 | } 92 | 93 | /** 94 | * Provide a read-only version of the {@link ByteBuffer} backing the data of this instance. 95 | *

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 | *

152 | *

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 childStreams = new ArrayList<>(); 69 | 70 | @Override 71 | abstract public void close() throws IOException; 72 | 73 | //region Stream positioning 74 | 75 | /** 76 | * Check if stream pointer is at the end of stream. 77 | * @return true if we are located at the end of the stream 78 | */ 79 | abstract public boolean isEof(); 80 | 81 | /** 82 | * Set stream pointer to designated position (int). 83 | * @param newPos new position (offset in bytes from the beginning of the stream) 84 | */ 85 | abstract public void seek(int newPos); 86 | 87 | /** 88 | * Set stream pointer to designated position (long). 89 | * @param newPos new position (offset in bytes from the beginning of the stream) 90 | */ 91 | abstract public void seek(long newPos); 92 | 93 | /** 94 | * Get current position of a stream pointer. 95 | * @return pointer position, number of bytes from the beginning of the stream 96 | */ 97 | abstract public int pos(); 98 | 99 | /** 100 | * Get total size of the stream in bytes. 101 | * @return size of the stream in bytes 102 | */ 103 | abstract public long size(); 104 | 105 | //endregion 106 | 107 | //region Reading 108 | 109 | //region Integer numbers 110 | 111 | //region Signed 112 | 113 | /** 114 | * Reads one signed 1-byte integer, returning it properly as Java's "byte" type. 115 | * @return 1-byte integer read from a stream 116 | */ 117 | abstract public byte readS1(); 118 | 119 | //region Big-endian 120 | 121 | abstract public short readS2be(); 122 | abstract public int readS4be(); 123 | abstract public long readS8be(); 124 | 125 | //endregion 126 | 127 | //region Little-endian 128 | 129 | abstract public short readS2le(); 130 | abstract public int readS4le(); 131 | abstract public long readS8le(); 132 | 133 | //endregion 134 | 135 | //endregion 136 | 137 | //region Unsigned 138 | 139 | abstract public int readU1(); 140 | 141 | //region Big-endian 142 | 143 | abstract public int readU2be(); 144 | 145 | abstract public long readU4be(); 146 | 147 | /** 148 | * Reads one unsigned 8-byte integer in big-endian encoding. As Java does not 149 | * have a primitive data type to accomodate it, we just reuse {@link #readS8be()}. 150 | * @return 8-byte signed integer (pretending to be unsigned) read from a stream 151 | */ 152 | public long readU8be() { 153 | return readS8be(); 154 | } 155 | 156 | //endregion 157 | 158 | //region Little-endian 159 | 160 | abstract public int readU2le(); 161 | 162 | abstract public long readU4le(); 163 | 164 | /** 165 | * Reads one unsigned 8-byte integer in little-endian encoding. As Java does not 166 | * have a primitive data type to accomodate it, we just reuse {@link #readS8le()}. 167 | * @return 8-byte signed integer (pretending to be unsigned) read from a stream 168 | */ 169 | public long readU8le() { 170 | return readS8le(); 171 | } 172 | 173 | //endregion 174 | 175 | //endregion 176 | 177 | //endregion 178 | 179 | //region Floating point numbers 180 | 181 | //region Big-endian 182 | 183 | abstract public float readF4be(); 184 | abstract public double readF8be(); 185 | 186 | //endregion 187 | 188 | //region Little-endian 189 | 190 | abstract public float readF4le(); 191 | abstract public double readF8le(); 192 | 193 | //endregion 194 | 195 | //endregion 196 | 197 | //region Unaligned bit values 198 | 199 | public void alignToByte() { 200 | bitsLeft = 0; 201 | bits = 0; 202 | } 203 | 204 | public long readBitsIntBe(int n) { 205 | bitsWriteMode = false; 206 | 207 | long res = 0; 208 | 209 | int bitsNeeded = n - bitsLeft; 210 | bitsLeft = -bitsNeeded & 7; // `-bitsNeeded mod 8` 211 | 212 | if (bitsNeeded > 0) { 213 | // 1 bit => 1 byte 214 | // 8 bits => 1 byte 215 | // 9 bits => 2 bytes 216 | int bytesNeeded = ((bitsNeeded - 1) / 8) + 1; // `ceil(bitsNeeded / 8)` 217 | byte[] buf = readBytesNotAligned(bytesNeeded); 218 | for (byte b : buf) { 219 | // `b` is signed byte, convert to unsigned using the "& 0xff" trick 220 | res = res << 8 | (b & 0xff); 221 | } 222 | 223 | long newBits = res; 224 | res = res >>> bitsLeft | (bitsNeeded < 64 ? bits << bitsNeeded : 0); 225 | bits = newBits; // will be masked at the end of the function 226 | } else { 227 | res = bits >>> -bitsNeeded; // shift unneeded bits out 228 | } 229 | 230 | long mask = (1L << bitsLeft) - 1; // `bitsLeft` is in range 0..7, so `(1L << 64)` does not have to be considered 231 | bits &= mask; 232 | 233 | return res; 234 | } 235 | 236 | /** 237 | * Unused since Kaitai Struct Compiler v0.9+ - compatibility with older versions 238 | * 239 | * @deprecated use {@link #readBitsIntBe(int)} instead 240 | */ 241 | @Deprecated 242 | public long readBitsInt(int n) { 243 | return readBitsIntBe(n); 244 | } 245 | 246 | public long readBitsIntLe(int n) { 247 | bitsWriteMode = false; 248 | 249 | long res = 0; 250 | int bitsNeeded = n - bitsLeft; 251 | 252 | if (bitsNeeded > 0) { 253 | // 1 bit => 1 byte 254 | // 8 bits => 1 byte 255 | // 9 bits => 2 bytes 256 | int bytesNeeded = ((bitsNeeded - 1) / 8) + 1; // `ceil(bitsNeeded / 8)` 257 | byte[] buf = readBytesNotAligned(bytesNeeded); 258 | for (int i = 0; i < bytesNeeded; i++) { 259 | // `buf[i]` is signed byte, convert to unsigned using the "& 0xff" trick 260 | res |= ((long) (buf[i] & 0xff)) << (i * 8); 261 | } 262 | 263 | // NB: in Java, bit shift operators on left-hand operand of type `long` work 264 | // as if the right-hand operand were subjected to `& 63` (`& 0b11_1111`) (see 265 | // https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.19), 266 | // so `res >>> 64` is equivalent to `res >>> 0` (but we don't want that) 267 | long newBits = bitsNeeded < 64 ? res >>> bitsNeeded : 0; 268 | res = res << bitsLeft | bits; 269 | bits = newBits; 270 | } else { 271 | res = bits; 272 | bits >>>= n; 273 | } 274 | 275 | bitsLeft = -bitsNeeded & 7; // `-bitsNeeded mod 8` 276 | 277 | if (n < 64) { 278 | long mask = (1L << n) - 1; 279 | res &= mask; 280 | } 281 | // if `n == 64`, do nothing 282 | return res; 283 | } 284 | 285 | //endregion 286 | 287 | //region Byte arrays 288 | 289 | /** 290 | * Reads designated number of bytes from the stream. 291 | * @param n number of bytes to read 292 | * @return read bytes as byte array 293 | */ 294 | public byte[] readBytes(long n) { 295 | alignToByte(); 296 | return readBytesNotAligned(n); 297 | } 298 | 299 | /** 300 | * Internal method to read the specified number of bytes from the stream. Unlike 301 | * {@link #readBytes(long)}, it doesn't align the bit position to the next byte 302 | * boundary. 303 | * @param n number of bytes to read 304 | * @return read bytes as a byte array 305 | */ 306 | abstract protected byte[] readBytesNotAligned(long n); 307 | 308 | /** 309 | * Reads all the remaining bytes in a stream as byte array. 310 | * @return all remaining bytes in a stream as byte array 311 | */ 312 | abstract public byte[] readBytesFull(); 313 | 314 | abstract public byte[] readBytesTerm(byte term, boolean includeTerm, boolean consumeTerm, boolean eosError); 315 | 316 | abstract public byte[] readBytesTermMulti(byte[] term, boolean includeTerm, boolean consumeTerm, boolean eosError); 317 | 318 | /** 319 | * Checks that next bytes in the stream match match expected fixed byte array. 320 | * It does so by determining number of bytes to compare, reading them, and doing 321 | * the actual comparison. If they differ, throws a {@link UnexpectedDataError} 322 | * runtime exception. 323 | * @param expected contents to be expected 324 | * @return read bytes as byte array, which are guaranteed to equal to expected 325 | * @throws UnexpectedDataError if read data from stream isn't equal to given data 326 | * @deprecated Not used anymore in favour of validators. 327 | */ 328 | @Deprecated 329 | public byte[] ensureFixedContents(byte[] expected) { 330 | byte[] actual = readBytes(expected.length); 331 | if (!Arrays.equals(actual, expected)) 332 | throw new UnexpectedDataError(actual, expected); 333 | return actual; 334 | } 335 | 336 | public static byte[] bytesStripRight(byte[] bytes, byte padByte) { 337 | int newLen = bytes.length; 338 | while (newLen > 0 && bytes[newLen - 1] == padByte) 339 | newLen--; 340 | return Arrays.copyOf(bytes, newLen); 341 | } 342 | 343 | public static byte[] bytesTerminate(byte[] bytes, byte term, boolean includeTerm) { 344 | int newLen = 0; 345 | int maxLen = bytes.length; 346 | while (newLen < maxLen && bytes[newLen] != term) 347 | newLen++; 348 | if (includeTerm && newLen < maxLen) 349 | newLen++; 350 | return Arrays.copyOf(bytes, newLen); 351 | } 352 | 353 | public static byte[] bytesTerminateMulti(byte[] bytes, byte[] term, boolean includeTerm) { 354 | int unitSize = term.length; 355 | if (unitSize == 0) { 356 | return new byte[0]; 357 | } 358 | int len = bytes.length; 359 | int iTerm = 0; 360 | for (int iBytes = 0; iBytes < len;) { 361 | if (bytes[iBytes] != term[iTerm]) { 362 | iBytes += unitSize - iTerm; 363 | iTerm = 0; 364 | continue; 365 | } 366 | iBytes++; 367 | iTerm++; 368 | if (iTerm == unitSize) { 369 | return Arrays.copyOf(bytes, iBytes - (includeTerm ? 0 : unitSize)); 370 | } 371 | } 372 | return Arrays.copyOf(bytes, bytes.length); 373 | } 374 | 375 | /** 376 | * Checks if supplied number of bytes is a valid number of elements for Java 377 | * byte array: converts it to int, if it is, or throws an exception if it is not. 378 | * @param n number of bytes for byte array as long 379 | * @return number of bytes, converted to int 380 | */ 381 | protected int toByteArrayLength(long n) { 382 | if (n > Integer.MAX_VALUE) { 383 | throw new IllegalArgumentException( 384 | "Java byte arrays can be indexed only up to 31 bits, but " + n + " size was requested" 385 | ); 386 | } 387 | if (n < 0) { 388 | throw new IllegalArgumentException( 389 | "Byte array size can't be negative, but " + n + " size was requested" 390 | ); 391 | } 392 | return (int) n; 393 | } 394 | 395 | //endregion 396 | 397 | //endregion 398 | 399 | //region Writing 400 | 401 | protected void ensureBytesLeftToWrite(long n, long pos) { 402 | long bytesLeft = size() - pos; 403 | if (n > bytesLeft) { 404 | throw new RuntimeException( 405 | new EOFException("requested to write " + n + " bytes, but only " + bytesLeft + " bytes left in the stream") 406 | ); 407 | } 408 | } 409 | 410 | //region Integer numbers 411 | 412 | //region Signed 413 | 414 | /** 415 | * Writes one signed 1-byte integer. 416 | */ 417 | abstract public void writeS1(byte v); 418 | 419 | //region Big-endian 420 | 421 | abstract public void writeS2be(short v); 422 | 423 | abstract public void writeS4be(int v); 424 | 425 | abstract public void writeS8be(long v); 426 | 427 | //endregion 428 | 429 | //region Little-endian 430 | 431 | abstract public void writeS2le(short v); 432 | 433 | abstract public void writeS4le(int v); 434 | 435 | abstract public void writeS8le(long v); 436 | 437 | //endregion 438 | 439 | //endregion 440 | 441 | //region Unsigned 442 | 443 | public void writeU1(int v) { 444 | writeS1((byte) v); 445 | } 446 | 447 | //region Big-endian 448 | 449 | public void writeU2be(int v) { 450 | writeS2be((short) v); 451 | } 452 | 453 | public void writeU4be(long v) { 454 | writeS4be((int) v); 455 | } 456 | 457 | public void writeU8be(long v) { 458 | writeS8be(v); 459 | } 460 | 461 | //endregion 462 | 463 | //region Little-endian 464 | 465 | public void writeU2le(int v) { 466 | writeS2le((short) v); 467 | } 468 | 469 | public void writeU4le(long v) { 470 | writeS4le((int) v); 471 | } 472 | 473 | public void writeU8le(long v) { 474 | writeS8le(v); 475 | } 476 | 477 | //endregion 478 | 479 | //endregion 480 | 481 | //endregion 482 | 483 | //region Floating point numbers 484 | 485 | //region Big-endian 486 | 487 | abstract public void writeF4be(float v); 488 | 489 | abstract public void writeF8be(double v); 490 | 491 | //endregion 492 | 493 | //region Little-endian 494 | 495 | abstract public void writeF4le(float v); 496 | 497 | abstract public void writeF8le(double v); 498 | 499 | //endregion 500 | 501 | //endregion 502 | 503 | //region Unaligned bit values 504 | 505 | public void writeAlignToByte() { 506 | if (bitsLeft > 0) { 507 | byte b = (byte) bits; 508 | if (!bitsLe) { 509 | b <<= 8 - bitsLeft; 510 | } 511 | // See https://github.com/kaitai-io/kaitai_struct_python_runtime/blob/704995ac/kaitaistruct.py#L572-L596 512 | // for an explanation of why we call alignToByte() before 513 | // writeBytesNotAligned(). 514 | alignToByte(); 515 | writeBytesNotAligned(new byte[] { b }); 516 | } 517 | } 518 | 519 | /* 520 | Example 1 (bytesToWrite > 0): 521 | 522 | old bitsLeft = 5 523 | | | new bitsLeft = 18 mod 8 = 2 524 | / \ /\ 525 | |01101xxx|xxxxxxxx|xx......| 526 | \ \ / 527 | \ \__ n = 13 _/ 528 | \ / 529 | \____________/ 530 | bitsToWrite = 18 -> bytesToWrite = 2 531 | 532 | --- 533 | 534 | Example 2 (bytesToWrite == 0): 535 | 536 | old bitsLeft = 1 537 | | | 538 | \ / 539 | |01101100|1xxxxx..|........| 540 | / \___/\ 541 | / n = 5 \ 542 | /__________\ 543 | bitsToWrite = 6 -> bytesToWrite = 0, 544 | new bitsLeft = 6 mod 8 = 6 545 | */ 546 | public void writeBitsIntBe(int n, long val) { 547 | bitsLe = false; 548 | bitsWriteMode = true; 549 | 550 | if (n < 64) { 551 | long mask = (1L << n) - 1; 552 | val &= mask; 553 | } 554 | // if `n == 64`, do nothing 555 | 556 | int bitsToWrite = bitsLeft + n; 557 | int bytesNeeded = ((bitsToWrite - 1) / 8) + 1; // `ceil(bitsToWrite / 8)` 558 | 559 | // pos() respects the `bitsLeft` field (it returns the stream position 560 | // as if it were already aligned on a byte boundary), which ensures that 561 | // we report the same numbers of bytes here as readBitsInt*() methods 562 | // would. 563 | ensureBytesLeftToWrite(bytesNeeded - (bitsLeft > 0 ? 1 : 0), pos()); 564 | 565 | int bytesToWrite = bitsToWrite / 8; 566 | bitsLeft = bitsToWrite & 7; // `bitsToWrite mod 8` 567 | 568 | if (bytesToWrite > 0) { 569 | byte[] buf = new byte[bytesToWrite]; 570 | 571 | long mask = (1L << bitsLeft) - 1; // `bitsLeft` is in range 0..7, so `(1L << 64)` does not have to be considered 572 | long newBits = val & mask; 573 | val = val >>> bitsLeft | (n - bitsLeft < 64 ? bits << (n - bitsLeft) : 0); 574 | bits = newBits; 575 | 576 | for (int i = bytesToWrite - 1; i >= 0; i--) { 577 | buf[i] = (byte) (val & 0xff); 578 | val >>>= 8; 579 | } 580 | writeBytesNotAligned(buf); 581 | } else { 582 | bits = bits << n | val; 583 | } 584 | } 585 | 586 | /* 587 | Example 1 (bytesToWrite > 0): 588 | 589 | n = 13 590 | 591 | old bitsLeft = 5 592 | | | new bitsLeft = 18 mod 8 = 2 593 | / \ /\ 594 | |xxx01101|xxxxxxxx|......xx| 595 | \ / / / 596 | --------------- -- 597 | \ / 598 | bitsToWrite = 18 -> bytesToWrite = 2 599 | 600 | --- 601 | 602 | Example 2 (bytesToWrite == 0): 603 | 604 | old bitsLeft = 1 605 | | | 606 | \ / 607 | |01101100|..xxxxx1|........| 608 | /\___/ \ 609 | / n = 5 \ 610 | /__________\ 611 | bitsToWrite = 6 -> bytesToWrite = 0, 612 | new bitsLeft = 6 mod 8 = 6 613 | */ 614 | public void writeBitsIntLe(int n, long val) { 615 | bitsLe = true; 616 | bitsWriteMode = true; 617 | 618 | int bitsToWrite = bitsLeft + n; 619 | int bytesNeeded = ((bitsToWrite - 1) / 8) + 1; // `ceil(bitsToWrite / 8)` 620 | 621 | // pos() respects the `bitsLeft` field (it returns the stream position 622 | // as if it were already aligned on a byte boundary), which ensures that 623 | // we report the same numbers of bytes here as readBitsInt*() methods 624 | // would. 625 | ensureBytesLeftToWrite(bytesNeeded - (bitsLeft > 0 ? 1 : 0), pos()); 626 | 627 | int bytesToWrite = bitsToWrite / 8; 628 | int oldBitsLeft = bitsLeft; 629 | bitsLeft = bitsToWrite & 7; // `bitsToWrite mod 8` 630 | 631 | if (bytesToWrite > 0) { 632 | byte[] buf = new byte[bytesToWrite]; 633 | 634 | long newBits = n - bitsLeft < 64 ? val >>> (n - bitsLeft) : 0; 635 | val = val << oldBitsLeft | bits; 636 | bits = newBits; 637 | 638 | for (int i = 0; i < bytesToWrite; i++) { 639 | buf[i] = (byte) (val & 0xff); 640 | val >>>= 8; 641 | } 642 | writeBytesNotAligned(buf); 643 | } else { 644 | bits |= val << oldBitsLeft; 645 | } 646 | 647 | long mask = (1L << bitsLeft) - 1; // `bitsLeft` is in range 0..7, so `(1L << 64)` does not have to be considered 648 | bits &= mask; 649 | } 650 | 651 | //endregion 652 | 653 | //region Byte arrays 654 | 655 | /** 656 | * Writes given byte array to the stream. 657 | * @param buf byte array to write 658 | */ 659 | public void writeBytes(byte[] buf) { 660 | writeAlignToByte(); 661 | writeBytesNotAligned(buf); 662 | } 663 | 664 | /** 665 | * Internal method to write the given byte array to the stream. Unlike 666 | * {@link #writeBytes(byte[])}, it doesn't align the bit position to the next byte 667 | * boundary. 668 | * @param buf byte array to write 669 | */ 670 | abstract protected void writeBytesNotAligned(byte[] buf); 671 | 672 | public void writeBytesLimit(byte[] buf, long size, byte term, byte padByte) { 673 | int len = buf.length; 674 | writeBytes(buf); 675 | if (len < size) { 676 | writeS1(term); 677 | long padLen = size - len - 1; 678 | for (long i = 0; i < padLen; i++) 679 | writeS1(padByte); 680 | } else if (len > size) { 681 | throw new IllegalArgumentException( 682 | "Writing " + size + " bytes, but " + len + " bytes were given" 683 | ); 684 | } 685 | } 686 | 687 | public void writeStream(KaitaiStream other) { 688 | writeBytes(other.toByteArray()); 689 | } 690 | 691 | //endregion 692 | 693 | //endregion 694 | 695 | //region Byte array processing 696 | 697 | /** 698 | * Performs a XOR processing with given data, XORing every byte of input with a single 699 | * given value. 700 | * @param data data to process 701 | * @param key value to XOR with 702 | * @return processed data 703 | */ 704 | public static byte[] processXor(byte[] data, byte key) { 705 | int dataLen = data.length; 706 | byte[] r = new byte[dataLen]; 707 | for (int i = 0; i < dataLen; i++) 708 | r[i] = (byte) (data[i] ^ key); 709 | return r; 710 | } 711 | 712 | /** 713 | * Performs a XOR processing with given data, XORing every byte of input with a key 714 | * array, repeating key array many times, if necessary (i.e. if data array is longer 715 | * than key array). 716 | * @param data data to process 717 | * @param key array of bytes to XOR with 718 | * @return processed data 719 | */ 720 | public static byte[] processXor(byte[] data, byte[] key) { 721 | int dataLen = data.length; 722 | int valueLen = key.length; 723 | 724 | byte[] r = new byte[dataLen]; 725 | int j = 0; 726 | for (int i = 0; i < dataLen; i++) { 727 | r[i] = (byte) (data[i] ^ key[j]); 728 | j = (j + 1) % valueLen; 729 | } 730 | return r; 731 | } 732 | 733 | /** 734 | * Performs a circular left rotation shift for a given buffer by a given amount of bits, 735 | * using groups of groupSize bytes each time. Right circular rotation should be performed 736 | * using this procedure with corrected amount. 737 | * @param data source data to process 738 | * @param amount number of bits to shift by 739 | * @param groupSize number of bytes per group to shift 740 | * @return copy of source array with requested shift applied 741 | */ 742 | public static byte[] processRotateLeft(byte[] data, int amount, int groupSize) { 743 | byte[] r = new byte[data.length]; 744 | switch (groupSize) { 745 | case 1: 746 | for (int i = 0; i < data.length; i++) { 747 | byte bits = data[i]; 748 | // https://stackoverflow.com/a/19181827/487064 749 | r[i] = (byte) (((bits & 0xff) << amount) | ((bits & 0xff) >>> (8 - amount))); 750 | } 751 | break; 752 | default: 753 | throw new UnsupportedOperationException("unable to rotate group of " + groupSize + " bytes yet"); 754 | } 755 | return r; 756 | } 757 | 758 | private final static int ZLIB_BUF_SIZE = 4096; 759 | 760 | /** 761 | * Performs an unpacking ("inflation") of zlib-compressed data with usual zlib headers. 762 | * @param data data to unpack 763 | * @return unpacked data 764 | * @throws RuntimeException if data can't be decoded 765 | */ 766 | public static byte[] processZlib(byte[] data) { 767 | Inflater ifl = new Inflater(); 768 | ifl.setInput(data); 769 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 770 | byte buf[] = new byte[ZLIB_BUF_SIZE]; 771 | while (!ifl.finished()) { 772 | try { 773 | int decBytes = ifl.inflate(buf); 774 | baos.write(buf, 0, decBytes); 775 | } catch (DataFormatException e) { 776 | throw new RuntimeException(e); 777 | } 778 | } 779 | ifl.end(); 780 | return baos.toByteArray(); 781 | } 782 | 783 | public static byte[] unprocessZlib(byte[] data) { 784 | Deflater dfl = new Deflater(); 785 | dfl.setInput(data); 786 | dfl.finish(); 787 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 788 | byte buf[] = new byte[ZLIB_BUF_SIZE]; 789 | while (!dfl.finished()) { 790 | int decBytes = dfl.deflate(buf); 791 | baos.write(buf, 0, decBytes); 792 | } 793 | dfl.end(); 794 | return baos.toByteArray(); 795 | } 796 | 797 | //endregion 798 | 799 | //region Misc runtime operations 800 | 801 | /** 802 | * Reserves next `n` bytes from current stream as a KaitaiStream-compatible substream. 803 | * Substream has its own pointer and addressing in the range of [0, n) bytes. This 804 | * stream's pointer is advanced to the position right after this substream. 805 | * @param n number of bytes to reserve for a substream 806 | * @return substream covering n bytes from the current position 807 | */ 808 | abstract public KaitaiStream substream(long n); 809 | 810 | /** 811 | * Performs modulo operation between two integers: dividend `a` 812 | * and divisor `b`. Divisor `b` is expected to be positive. The 813 | * result is always 0 <= x <= b - 1. 814 | * @param a dividend 815 | * @param b divisor 816 | * @return result 817 | */ 818 | public static int mod(int a, int b) { 819 | if (b <= 0) 820 | throw new ArithmeticException("mod divisor <= 0"); 821 | int r = a % b; 822 | if (r < 0) 823 | r += b; 824 | return r; 825 | } 826 | 827 | /** 828 | * Performs modulo operation between two integers: dividend `a` 829 | * and divisor `b`. Divisor `b` is expected to be positive. The 830 | * result is always 0 <= x <= b - 1. 831 | * @param a dividend 832 | * @param b divisor 833 | * @return result 834 | */ 835 | public static long mod(long a, long b) { 836 | if (b <= 0) 837 | throw new ArithmeticException("mod divisor <= 0"); 838 | long r = a % b; 839 | if (r < 0) 840 | r += b; 841 | return r; 842 | } 843 | 844 | /** 845 | * Compares two byte arrays in lexicographical order. Makes extra effort 846 | * to compare bytes properly, as *unsigned* bytes, i.e. [0x90] would be 847 | * greater than [0x10]. 848 | * @param a first byte array to compare 849 | * @param b second byte array to compare 850 | * @return negative number if a < b, 0 if a == b, positive number if a > b 851 | * @see Comparable#compareTo(Object) 852 | */ 853 | public static int byteArrayCompare(byte[] a, byte[] b) { 854 | if (a == b) 855 | return 0; 856 | int al = a.length; 857 | int bl = b.length; 858 | int minLen = Math.min(al, bl); 859 | for (int i = 0; i < minLen; i++) { 860 | int cmp = (a[i] & 0xff) - (b[i] & 0xff); 861 | if (cmp != 0) 862 | return cmp; 863 | } 864 | 865 | // Reached the end of at least one of the arrays 866 | if (al == bl) { 867 | return 0; 868 | } else { 869 | return al - bl; 870 | } 871 | } 872 | 873 | /** 874 | * Finds the minimal byte in a byte array, treating bytes as 875 | * unsigned values. 876 | * @param b byte array to scan 877 | * @return minimal byte in byte array as integer 878 | */ 879 | public static int byteArrayMin(byte[] b) { 880 | int min = Integer.MAX_VALUE; 881 | for (int i = 0; i < b.length; i++) { 882 | int value = b[i] & 0xff; 883 | if (value < min) 884 | min = value; 885 | } 886 | return min; 887 | } 888 | 889 | /** 890 | * Finds the maximal byte in a byte array, treating bytes as 891 | * unsigned values. 892 | * @param b byte array to scan 893 | * @return maximal byte in byte array as integer 894 | */ 895 | public static int byteArrayMax(byte[] b) { 896 | int max = 0; 897 | for (int i = 0; i < b.length; i++) { 898 | int value = b[i] & 0xff; 899 | if (value > max) 900 | max = value; 901 | } 902 | return max; 903 | } 904 | 905 | /** 906 | * Returns the index of the first occurrence of the specified byte in a byte 907 | * array, or -1 if this byte array does not contain the byte. 908 | * 909 | * @param arr byte array to search in 910 | * @param b byte to search for 911 | * @return index of the first occurrence of the specified byte in the byte 912 | * array, or -1 if this byte array does not contain the byte 913 | * @see java.util.List#indexOf(Object) 914 | * @see String#indexOf(int) 915 | */ 916 | public static int byteArrayIndexOf(byte[] arr, byte b) { 917 | int len = arr.length; 918 | for (int i = 0; i < len; i++) { 919 | if (arr[i] == b) { 920 | return i; 921 | } 922 | } 923 | return -1; 924 | } 925 | 926 | static String byteArrayToHex(byte[] arr) { 927 | StringBuilder sb = new StringBuilder("["); 928 | for (int i = 0; i < arr.length; i++) { 929 | if (i > 0) 930 | sb.append(' '); 931 | sb.append(String.format("%02x", arr[i])); 932 | } 933 | sb.append(']'); 934 | return sb.toString(); 935 | } 936 | 937 | //endregion 938 | 939 | public byte[] toByteArray() { 940 | long pos = pos(); 941 | seek(0); 942 | byte[] r = readBytesFull(); 943 | seek(pos); 944 | return r; 945 | } 946 | 947 | public abstract static class WriteBackHandler { 948 | protected final long pos; 949 | 950 | public WriteBackHandler(long pos) { 951 | this.pos = pos; 952 | } 953 | 954 | public void writeBack(KaitaiStream parent) { 955 | parent.seek(pos); 956 | write(parent); 957 | } 958 | 959 | protected abstract void write(KaitaiStream parent); 960 | } 961 | 962 | public void setWriteBackHandler(WriteBackHandler handler) { 963 | writeBackHandler = handler; 964 | } 965 | 966 | public void addChildStream(KaitaiStream child) { 967 | childStreams.add(child); 968 | } 969 | 970 | public void writeBackChildStreams() { 971 | writeBackChildStreams(null); 972 | } 973 | 974 | protected void writeBackChildStreams(KaitaiStream parent) { 975 | final long _pos = pos(); 976 | for (KaitaiStream child : childStreams) { 977 | child.writeBackChildStreams(this); 978 | } 979 | childStreams.clear(); 980 | seek(_pos); 981 | if (parent != null) { 982 | writeBack(parent); 983 | } 984 | } 985 | 986 | protected void writeBack(KaitaiStream parent) { 987 | writeBackHandler.writeBack(parent); 988 | } 989 | 990 | /** 991 | * Exception class for an error that occurs when some fixed content 992 | * was expected to appear, but actual data read was different. 993 | * 994 | * @deprecated Not used anymore in favour of {@code Validation*}-exceptions. 995 | */ 996 | @Deprecated 997 | public static class UnexpectedDataError extends RuntimeException { 998 | public UnexpectedDataError(byte[] actual, byte[] expected) { 999 | super( 1000 | "Unexpected fixed contents: got " + byteArrayToHex(actual) + 1001 | ", was waiting for " + byteArrayToHex(expected) 1002 | ); 1003 | } 1004 | 1005 | private static String byteArrayToHex(byte[] arr) { 1006 | StringBuilder sb = new StringBuilder(); 1007 | for (int i = 0; i < arr.length; i++) { 1008 | if (i > 0) 1009 | sb.append(' '); 1010 | sb.append(String.format("%02x", arr[i])); 1011 | } 1012 | return sb.toString(); 1013 | } 1014 | } 1015 | 1016 | /** 1017 | * Error that occurs when default endianness should be decided with a 1018 | * switch, but nothing matches (although using endianness expression 1019 | * implies that there should be some positive result). 1020 | */ 1021 | public static class UndecidedEndiannessError extends RuntimeException {} 1022 | 1023 | /** 1024 | * Common ancestor for all error originating from Kaitai Struct usage. 1025 | * Stores KSY source path, pointing to an element supposedly guilty of 1026 | * an error. 1027 | */ 1028 | public static class KaitaiStructError extends RuntimeException { 1029 | public KaitaiStructError(String msg, String srcPath) { 1030 | super(srcPath + ": " + msg); 1031 | this.srcPath = srcPath; 1032 | } 1033 | 1034 | protected String srcPath; 1035 | } 1036 | 1037 | /** 1038 | * Common ancestor for all validation failures. Stores pointer to 1039 | * KaitaiStream IO object which was involved in an error. 1040 | */ 1041 | public static class ValidationFailedError extends KaitaiStructError { 1042 | public ValidationFailedError(String msg, KaitaiStream io, String srcPath) { 1043 | super((io != null ? "at pos " + io.pos() + ": " : "") + "validation failed: " + msg, srcPath); 1044 | this.io = io; 1045 | } 1046 | 1047 | protected KaitaiStream io; 1048 | } 1049 | 1050 | /** 1051 | * Signals validation failure: we required "actual" value to be equal to 1052 | * "expected", but it turned out that it's not. 1053 | */ 1054 | public static class ValidationNotEqualError extends ValidationFailedError { 1055 | public ValidationNotEqualError(byte[] expected, byte[] actual, KaitaiStream io, String srcPath) { 1056 | super("not equal, expected " + byteArrayToHex(expected) + ", but got " + byteArrayToHex(actual), io, srcPath); 1057 | } 1058 | 1059 | public ValidationNotEqualError(Object expected, Object actual, KaitaiStream io, String srcPath) { 1060 | super("not equal, expected " + expected + ", but got " + actual, io, srcPath); 1061 | } 1062 | 1063 | protected Object expected; 1064 | protected Object actual; 1065 | } 1066 | 1067 | public static class ValidationLessThanError extends ValidationFailedError { 1068 | public ValidationLessThanError(byte[] expected, byte[] actual, KaitaiStream io, String srcPath) { 1069 | super("not in range, min " + byteArrayToHex(expected) + ", but got " + byteArrayToHex(actual), io, srcPath); 1070 | } 1071 | 1072 | public ValidationLessThanError(Object min, Object actual, KaitaiStream io, String srcPath) { 1073 | super("not in range, min " + min + ", but got " + actual, io, srcPath); 1074 | } 1075 | 1076 | protected Object min; 1077 | protected Object actual; 1078 | } 1079 | 1080 | public static class ValidationGreaterThanError extends ValidationFailedError { 1081 | public ValidationGreaterThanError(byte[] expected, byte[] actual, KaitaiStream io, String srcPath) { 1082 | super("not in range, max " + byteArrayToHex(expected) + ", but got " + byteArrayToHex(actual), io, srcPath); 1083 | } 1084 | 1085 | public ValidationGreaterThanError(Object max, Object actual, KaitaiStream io, String srcPath) { 1086 | super("not in range, max " + max + ", but got " + actual, io, srcPath); 1087 | } 1088 | 1089 | protected Object max; 1090 | protected Object actual; 1091 | } 1092 | 1093 | public static class ValidationNotAnyOfError extends ValidationFailedError { 1094 | public ValidationNotAnyOfError(Object actual, KaitaiStream io, String srcPath) { 1095 | super("not any of the list, got " + actual, io, srcPath); 1096 | } 1097 | 1098 | protected Object actual; 1099 | } 1100 | 1101 | public static class ValidationNotInEnumError extends ValidationFailedError { 1102 | public ValidationNotInEnumError(Object actual, KaitaiStream io, String srcPath) { 1103 | super("not in the enum, got " + actual, io, srcPath); 1104 | } 1105 | 1106 | protected Object actual; 1107 | } 1108 | 1109 | public static class ValidationExprError extends ValidationFailedError { 1110 | public ValidationExprError(Object actual, KaitaiStream io, String srcPath) { 1111 | super("not matching the expression, got " + actual, io, srcPath); 1112 | } 1113 | 1114 | protected Object actual; 1115 | } 1116 | } 1117 | -------------------------------------------------------------------------------- /src/main/java/io/kaitai/struct/KaitaiStruct.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 | * Common base class for all structured generated by Kaitai Struct. 28 | * Stores stream object that this object was parsed from in {@link #_io}. 29 | */ 30 | public class KaitaiStruct { 31 | /** 32 | * Stream object that this KaitaiStruct-based structure was parsed from. 33 | */ 34 | protected KaitaiStream _io; 35 | protected KaitaiStruct _parent; 36 | 37 | public KaitaiStruct(KaitaiStream _io) { 38 | this._io = _io; 39 | } 40 | 41 | public KaitaiStream _io() { return _io; } 42 | public KaitaiStruct _parent() { return _parent; } 43 | 44 | /** 45 | * KaitaiStruct object that supports reading from a supplied stream object. 46 | */ 47 | public abstract static class ReadOnly extends KaitaiStruct { 48 | public ReadOnly(KaitaiStream _io) { 49 | super(_io); 50 | } 51 | public abstract void _read(); 52 | } 53 | 54 | /** 55 | * KaitaiStruct object that supports both reading from a given stream 56 | * object, and writing to a pre-supplied stream object or to a 57 | * stream object given explicitly. This also defines a few useful 58 | * shortcut methods. 59 | */ 60 | public abstract static class ReadWrite extends ReadOnly { 61 | public ReadWrite(KaitaiStream _io) { 62 | super(_io); 63 | } 64 | public abstract void _write_Seq(); 65 | public abstract void _check(); 66 | public abstract void _fetchInstances(); // FIXME: perhaps move directly into KaitaiStruct 67 | 68 | public void _write() { 69 | _write_Seq(); 70 | _fetchInstances(); 71 | _io.writeBackChildStreams(); 72 | } 73 | 74 | public void _write(KaitaiStream io) { 75 | this._io = io; 76 | _write(); 77 | } 78 | 79 | public void _write_Seq(KaitaiStream io) { 80 | this._io = io; 81 | _write_Seq(); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/io/kaitai/struct/RandomAccessFileKaitaiStream.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.EOFException; 28 | import java.io.IOException; 29 | import java.io.RandomAccessFile; 30 | import java.util.Arrays; 31 | 32 | /** 33 | * An implementation of {@link KaitaiStream} backed by a {@link RandomAccessFile}. 34 | * 35 | * Allows reading from local files. Generally, one would want to use 36 | * {@link ByteBufferKaitaiStream} instead, as it most likely would be faster, 37 | * but there are two situations when one should consider this one instead: 38 | * 39 | *

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 | * 60 | * 61 | * 62 | * 65 | * 66 | * 67 | * 70 | * 71 | * 72 | * 75 | * 76 | * 77 | * 80 | * 81 | *
Position tracking info.
TypeFieldDescription
{@code Map}{@code _attrStart}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
{@code Map}{@code _attrEnd}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
{@code Map>}{@code _arrStart}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
{@code Map>}{@code _arrEnd}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
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 | } --------------------------------------------------------------------------------