1 package nom.tam.fits.utilities; 2 3 /*- 4 * #%L 5 * nom.tam FITS library 6 * %% 7 * Copyright (C) 1996 - 2024 nom-tam-fits 8 * %% 9 * This is free and unencumbered software released into the public domain. 10 * 11 * Anyone is free to copy, modify, publish, use, compile, sell, or 12 * distribute this software, either in source code form or as a compiled 13 * binary, for any purpose, commercial or non-commercial, and by any 14 * means. 15 * 16 * In jurisdictions that recognize copyright laws, the author or authors 17 * of this software dedicate any and all copyright interest in the 18 * software to the public domain. We make this dedication for the benefit 19 * of the public at large and to the detriment of our heirs and 20 * successors. We intend this dedication to be an overt act of 21 * relinquishment in perpetuity of all present and future rights to this 22 * software under copyright law. 23 * 24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 28 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 29 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 30 * OTHER DEALINGS IN THE SOFTWARE. 31 * #L% 32 */ 33 34 import java.io.DataInputStream; 35 import java.io.EOFException; 36 import java.io.IOException; 37 import java.io.InputStream; 38 import java.io.PipedInputStream; 39 import java.io.PipedOutputStream; 40 import java.nio.ByteBuffer; 41 import java.nio.ByteOrder; 42 import java.nio.IntBuffer; 43 import java.nio.charset.StandardCharsets; 44 import java.util.Arrays; 45 46 import nom.tam.fits.BasicHDU; 47 import nom.tam.fits.Data; 48 import nom.tam.fits.FitsDate; 49 import nom.tam.fits.FitsElement; 50 import nom.tam.fits.FitsException; 51 import nom.tam.fits.Header; 52 import nom.tam.fits.HeaderCard; 53 import nom.tam.util.FitsIO; 54 import nom.tam.util.FitsOutputStream; 55 import nom.tam.util.RandomAccess; 56 57 import static nom.tam.fits.header.Checksum.CHECKSUM; 58 import static nom.tam.fits.header.Checksum.DATASUM; 59 60 /** 61 * <p> 62 * Helper class for dealing with FITS checksums. It implements the Seaman-Pence 32-bit 1's complement checksum 63 * calculation. The implementation accumulates in two 64-bit integer values the low and high-order 16-bits of adjacent 64 * 4-byte groups. A carry-over of bits are calculated only at the end of the loop. Given the use of 64-bit accumulators, 65 * overflow would occur approximately at 280 billion short values. 66 * </p> 67 * <p> 68 * This updated version of the class is a little more flexible than the prior incarnation. Specifically it allows 69 * incremental updates to data sums, and provides methods for dealing with partial checksums (e.g. from modified 70 * segments of the data), which may be used e.g. to calculate delta sums from changed blocks of data. The new 71 * implementation provides methods for decoding encoded checksums, and for calculating checksums directly from files 72 * without the need to read potentially huge data into RAM first, and for easily accessing the values stored in FITS 73 * headers. 74 * </p> 75 * <p> 76 * See <a href="http://arxiv.org/abs/1201.1345" target="@top">FITS Checksum Proposal</a> 77 * </p> 78 * <p> 79 * Note this class probably should be in the <code>nom.tam.util</code> package, but somehow it ended up here and now we 80 * are stuck with it. 81 * </p> 82 * 83 * @author R J Mather, Tony Johnson, Attila Kovacs 84 * 85 * @see nom.tam.fits.header.Checksum#CHECKSUM 86 */ 87 public final class FitsCheckSum { 88 89 private static final int CHECKSUM_BLOCK_SIZE = 4; 90 private static final int CHECKSUM_BLOCK_MASK = CHECKSUM_BLOCK_SIZE - 1; 91 private static final int CHECKSUM_STRING_SIZE = 16; 92 private static final int SHIFT_2_BYTES = 16; 93 private static final int MASK_2_BYTES = 0xffff; 94 private static final int MASK_4_BYTES = 0xffffffff; 95 private static final int MASK_BYTE = 0xff; 96 private static final int ASCII_ZERO = '0'; 97 private static final int BUFFER_SIZE = 0x8000; // 32 kB 98 99 private static final int[] SELECT_BYTE = {24, 16, 8, 0}; 100 private static final String EXCLUDE = ":;<=>?@[\\]^_`"; 101 private static final String CHECKSUM_DEFAULT = "0000000000000000"; 102 103 /** The expected checksum for a HDU that already contains a valid CHECKSUM keyword */ 104 public static final long HDU_CHECKSUM = 0xffffffffL; 105 106 private FitsCheckSum() { 107 } 108 109 /** 110 * Internal class for accumulating FITS checksums. 111 */ 112 private static class Checksum { 113 private long h, l; 114 115 Checksum(long prior) { 116 h = (prior >>> SHIFT_2_BYTES) & MASK_2_BYTES; 117 l = prior & MASK_2_BYTES; 118 } 119 120 void add(int i) { 121 h += i >>> SHIFT_2_BYTES; 122 l += i & MASK_2_BYTES; 123 } 124 125 long getChecksum() { 126 long hi = h & MASK_4_BYTES; // as unsigned 32-bit integer 127 long lo = l & MASK_4_BYTES; 128 129 for (;;) { 130 long hicarry = hi >>> SHIFT_2_BYTES; 131 long locarry = lo >>> SHIFT_2_BYTES; 132 if ((hicarry | locarry) == 0) { 133 break; 134 } 135 hi = (hi & MASK_2_BYTES) + locarry; 136 lo = (lo & MASK_2_BYTES) + hicarry; 137 } 138 return (hi << SHIFT_2_BYTES) | lo; 139 } 140 141 } 142 143 private static class PipeWriter extends Thread { 144 private Exception exception; 145 private PipedOutputStream out; 146 private FitsElement data; 147 148 PipeWriter(FitsElement data, PipedInputStream in) throws IOException { 149 this.data = data; 150 out = new PipedOutputStream(in); 151 } 152 153 @Override 154 public void run() { 155 exception = null; 156 try (FitsOutputStream fos = new FitsOutputStream(out)) { 157 if (data instanceof Header) { 158 ((Header) data).writeUnchecked(fos); 159 } else { 160 data.write(fos); 161 } 162 } catch (Exception e) { 163 exception = e; 164 } 165 } 166 167 public Exception getException() { 168 return exception; 169 } 170 } 171 172 /** 173 * Computes the checksum for a byte array. 174 * 175 * @param data the byte sequence for which to calculate a chekcsum 176 * 177 * @return the 32bit checksum in the range from 0 to 2^32-1 178 * 179 * @see #checksum(byte[], int, int) 180 */ 181 public static long checksum(byte[] data) { 182 return checksum(ByteBuffer.wrap(data)); 183 } 184 185 /** 186 * Computes the checksum for a segment of a byte array. 187 * 188 * @param data the byte sequence for which to calculate a chekcsum 189 * @param from Stating index of bytes to include in checksum calculation 190 * @param to Ending index (exclusive) of bytes to include in checksum 191 * 192 * @return the 32-bit checksum in the range from 0 to 2^32-1 193 * 194 * @see #checksum(RandomAccess, long, long) 195 * 196 * @since 1.17 197 */ 198 public static long checksum(byte[] data, int from, int to) { 199 return checksum(ByteBuffer.wrap(data, from, to)); 200 } 201 202 /** 203 * Computes the checksum from a buffer. This method can be used to calculate partial checksums for any data that can 204 * be wrapped into a buffer one way or another. As such it is suitable for calculating partial sums from segments of 205 * data that can be used to update datasums incrementally (e.g. by incrementing the datasum with the difference of 206 * the checksum of the new data segment vs the old data segment), or for updating the checksum for new data that has 207 * been added to the existing data (e.g. new rows in a binary table) as long as the modified data segment is a 208 * multiple of 4 bytes. 209 * 210 * @param data the buffer for which to calculate a (partial) checksum 211 * 212 * @return the computed 32-bit unsigned checksum as a Java <code>long</code> 213 * 214 * @since 1.17 215 * 216 * @see #checksum(Data) 217 * @see #checksum(Header) 218 * @see #sumOf(long...) 219 * @see #differenceOf(long, long) 220 */ 221 public static long checksum(ByteBuffer data) { 222 Checksum sum = new Checksum(0); 223 if (!(data.remaining() % CHECKSUM_BLOCK_SIZE == 0)) { 224 throw new IllegalArgumentException("fits blocks must always be divisible by 4"); 225 } 226 data.position(0); 227 data.order(ByteOrder.BIG_ENDIAN); 228 IntBuffer iData = data.asIntBuffer(); 229 while (iData.hasRemaining()) { 230 sum.add(iData.get()); 231 } 232 return sum.getChecksum(); 233 } 234 235 private static long checksum(InputStream in) throws IOException { 236 Checksum sum = new Checksum(0); 237 DataInputStream din = new DataInputStream(in); 238 239 for (;;) { 240 try { 241 sum.add(din.readInt()); 242 } catch (EOFException e) { 243 break; 244 } 245 } 246 247 return sum.getChecksum(); 248 } 249 250 private static long compute(final FitsElement data) throws FitsException { 251 try (PipedInputStream in = new PipedInputStream()) { 252 PipeWriter writer = new PipeWriter(data, in); 253 writer.start(); 254 255 long sum = checksum(in); 256 257 writer.join(); 258 if (writer.getException() != null) { 259 throw writer.getException(); 260 } 261 262 return sum; 263 } catch (Exception e) { 264 if (e instanceof FitsException) { 265 throw (FitsException) e; 266 } 267 throw new FitsException("Exception while checksumming FITS element: " + e.getMessage(), e); 268 } 269 } 270 271 /** 272 * Computes the checksum for a FITS data object, e.g. to be used with {@link #setDatasum(Header, long)}. This call 273 * will always calculate the checksum for the data in memory, and as such load deferred mode data into RAM as 274 * necessary to perform the calculation. If you rather not load a huge amount of data into RAM, you might consider 275 * using {@link #checksum(RandomAccess, long, long)} instead. 276 * 277 * @deprecated Use {@link BasicHDU#verifyIntegrity()} instead. 278 * 279 * @param data The FITS data object for which to calculate a checksum 280 * 281 * @return The checksum of the data 282 * 283 * @throws FitsException If there was an error serializing the data object 284 * 285 * @see Data#calcChecksum() 286 * @see BasicHDU#verifyDataIntegrity() 287 * @see #checksum(RandomAccess, long, long) 288 * @see #setDatasum(Header, long) 289 * @see #setChecksum(BasicHDU) 290 * 291 * @since 1.17 292 */ 293 public static long checksum(Data data) throws FitsException { 294 return compute(data); 295 } 296 297 /** 298 * Computes the checksum for a FITS header. It returns the checksum for the header as is, without attempting to 299 * validate the header, or modifying it in any way. 300 * 301 * @deprecated Use {@link BasicHDU#verifyIntegrity()} instead. 302 * 303 * @param header The FITS header object for which to calculate a checksum 304 * 305 * @return The checksum of the data 306 * 307 * @throws FitsException If there was an error serializing the FITS header 308 * 309 * @see #checksum(Data) 310 * 311 * @since 1.17 312 */ 313 public static long checksum(Header header) throws FitsException { 314 return compute(header); 315 } 316 317 /** 318 * Calculates the FITS checksum for a HDU, e.g to compare agains the value stored under the CHECKSUM header keyword. 319 * The 320 * 321 * @param hdu The Fits HDU for which to calculate a checksum, including both the header and data 322 * segments. 323 * 324 * @return The calculated checksum for the given HDU. 325 * 326 * @throws FitsException if there was an error accessing the contents of the HDU. 327 * 328 * @see BasicHDU#verifyIntegrity() 329 * @see #checksum(Data) 330 * @see #sumOf(long...) 331 * 332 * @deprecated Use {@link BasicHDU#verifyIntegrity()} instead to verify checksums. 333 * 334 * @since 1.17 335 */ 336 public static long checksum(BasicHDU<?> hdu) throws FitsException { 337 return sumOf(checksum(hdu.getHeader()), checksum(hdu.getData())); 338 } 339 340 /** 341 * Computes the checksum directly from a region of a random access file, by buffering moderately sized chunks from 342 * the file as necessary. The file may be very large, up to the full range of 64-bit addresses. 343 * 344 * @param f the random access file, from which to compute a checksum 345 * @param from the starting position in the file, where to start computing the checksum from. 346 * @param size the number of bytes in the file to include in the checksum calculation. 347 * 348 * @return the checksum for the given segment of the file 349 * 350 * @throws IOException if there was a problem accessing the file during the computation. 351 * 352 * @since 1.17 353 * 354 * @see #checksum(ByteBuffer) 355 * @see #checksum(Data) 356 */ 357 public static long checksum(RandomAccess f, long from, long size) throws IOException { 358 if (f == null) { 359 return 0L; 360 } 361 362 int len = (int) Math.min(BUFFER_SIZE, size); 363 byte[] buf = new byte[len]; 364 long oldpos = f.position(); 365 f.position(from); 366 long sum = 0; 367 368 while (size > 0) { 369 len = (int) Math.min(BUFFER_SIZE, size); 370 len = f.read(buf, 0, len); 371 sum = sumOf(sum, checksum(buf, 0, len)); 372 from += len; 373 size -= len; 374 } 375 376 f.position(oldpos); 377 return sum; 378 } 379 380 /** 381 * @deprecated Use {@link #encode(long, boolean)} instead. 382 * 383 * @param c The calculated 32-bit (unsigned) checksum 384 * @param compl Whether to complement the raw checksum (as defined by the convention). 385 * 386 * @return The encoded checksum, suitably encoded for use with the CHECKSUM header 387 */ 388 @Deprecated 389 public static String checksumEnc(final long c, final boolean compl) { 390 return encode(c, compl); 391 } 392 393 /** 394 * Encodes the complemented checksum. It is the same as <code>encode(checksum, true)</code>. 395 * 396 * @param checksum The calculated 32-bit (unsigned) checksum 397 * 398 * @return The encoded checksum, suitably encoded for use with the CHECKSUM header 399 * 400 * @see #decode(String) 401 * 402 * @since 1.17 403 */ 404 public static String encode(long checksum) { 405 return encode(checksum, true); 406 } 407 408 /** 409 * Encodes the given checksum as is or by its complement. 410 * 411 * @param checksum The calculated 32-bit (unsigned) checksum 412 * @param compl If <code>true</code> the complement of the specified value will be encoded. Otherwise, the value 413 * as is will be encoded. (FITS normally uses the complemenyed value). 414 * 415 * @return The encoded checksum, suitably encoded for use with the CHECKSUM header 416 * 417 * @see #decode(String, boolean) 418 * 419 * @since 1.17 420 */ 421 public static String encode(long checksum, boolean compl) { 422 if (compl) { 423 checksum = ~checksum & FitsIO.INTEGER_MASK; 424 } 425 426 final byte[] asc = new byte[CHECKSUM_STRING_SIZE]; 427 final byte[] ch = new byte[CHECKSUM_BLOCK_SIZE]; 428 final int sum = (int) checksum; 429 430 for (int i = 0; i < CHECKSUM_BLOCK_SIZE; i++) { 431 // each byte becomes four 432 final int byt = MASK_BYTE & (sum >>> SELECT_BYTE[i]); 433 434 Arrays.fill(ch, (byte) ((byt >>> 2) + ASCII_ZERO)); // quotient 435 ch[0] += (byte) (byt & CHECKSUM_BLOCK_MASK); // remainder 436 437 for (int j = 0; j < CHECKSUM_BLOCK_SIZE; j += 2) { 438 while (EXCLUDE.indexOf(ch[j]) >= 0 || EXCLUDE.indexOf(ch[j + 1]) >= 0) { 439 ch[j]++; 440 ch[j + 1]--; 441 } 442 } 443 444 for (int j = 0; j < CHECKSUM_BLOCK_SIZE; j++) { 445 int k = CHECKSUM_BLOCK_SIZE * j + i + 1; 446 k = (k < CHECKSUM_STRING_SIZE) ? k : 0; // rotate right 447 asc[k] = ch[j]; 448 } 449 } 450 451 return new String(asc, StandardCharsets.US_ASCII); 452 } 453 454 /** 455 * Decodes an encoded (and complemented) checksum. The same as <code>decode(encoded, true)</code>, and the the 456 * inverse of {@link #encode(long)}. 457 * 458 * @param encoded The encoded checksum (16 character string) 459 * 460 * @return The unsigned 32-bit integer complemeted checksum. 461 * 462 * @throws IllegalArgumentException if the checksum string is invalid (wrong length or contains illegal ASCII 463 * characters) 464 * 465 * @see #encode(long) 466 * 467 * @since 1.17 468 */ 469 public static long decode(String encoded) throws IllegalArgumentException { 470 return decode(encoded, true); 471 } 472 473 /** 474 * Decodes an encoded checksum, complementing it as required. It is the inverse of {@link #encode(long, boolean)}. 475 * 476 * @param encoded the encoded checksum (16 character string) 477 * @param compl whether to complement the checksum after decoding. Normally FITS uses 478 * complemented 32-bit checksums, so typically this optional argument should be 479 * <code>true</code>. 480 * 481 * @return The unsigned 32-bit integer checksum. 482 * 483 * @throws IllegalArgumentException if the checksum string is invalid (wrong length or contains illegal ASCII 484 * characters) 485 * 486 * @see #encode(long, boolean) 487 * 488 * @since 1.17 489 */ 490 public static long decode(String encoded, boolean compl) throws IllegalArgumentException { 491 byte[] bytes = encoded.getBytes(StandardCharsets.US_ASCII); 492 if (bytes.length != CHECKSUM_STRING_SIZE) { 493 throw new IllegalArgumentException("Bad checksum with " + bytes.length + " chars (expected 16)"); 494 } 495 // Rotate the bytes one to the left 496 byte tmp = bytes[0]; 497 System.arraycopy(bytes, 1, bytes, 0, CHECKSUM_STRING_SIZE - 1); 498 bytes[CHECKSUM_STRING_SIZE - 1] = tmp; 499 500 for (int i = 0; i < CHECKSUM_STRING_SIZE; i++) { 501 if (bytes[i] < ASCII_ZERO) { 502 throw new IllegalArgumentException("Bad checksum with illegal char " + Integer.toHexString(bytes[i]) 503 + " at pos " + i + " (ASCII below 0x30)"); 504 } 505 bytes[i] -= ASCII_ZERO; 506 } 507 508 ByteBuffer bb = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN); 509 510 long sum = (bb.getInt() + bb.getInt() + bb.getInt() + bb.getInt()); 511 return (compl ? ~sum : sum) & FitsIO.INTEGER_MASK; 512 } 513 514 /** 515 * Calculates the total checksum from partial sums. For example combining checksums from a header and data segment 516 * of a HDU, or for composing a data checksum from image tiles. 517 * 518 * @param parts The partial sums that are to be added together. 519 * 520 * @return The aggregated checksum as a 32-bit unsigned value. 521 * 522 * @see #differenceOf(long, long) 523 * 524 * @since 1.17 525 */ 526 public static long sumOf(long... parts) { 527 Checksum sum = new Checksum(0); 528 529 for (long part : parts) { 530 sum.h += part >>> SHIFT_2_BYTES; 531 sum.l += part & MASK_2_BYTES; 532 } 533 return sum.getChecksum(); 534 } 535 536 /** 537 * Subtracts a partial checksum from an aggregated total. One may use it, for example to update the datasum of a 538 * large data, when modifying only a small segment of it. Thus, one would first subtract the checksum of the old 539 * segment from tha prior datasum, and then add the checksum of the new data segment -- without hacing to 540 * recalculate the checksum for the entire data again. 541 * 542 * @deprecated Not foolproof, because of the carry over handling in checksums, some additionas are not 543 * reversible. E.g. 0xffffffff + 0xffffffff = 0xffffffff, but 0xffffffff - 0xffffffff = 0; 544 * 545 * @param total The total checksum. 546 * @param part The partial checksum to be subtracted from the total. 547 * 548 * @return The checksum after subtracting the partial sum, as a 32-bit unsigned value. 549 * 550 * @see #sumOf(long...) 551 * 552 * @since 1.17 553 */ 554 public static long differenceOf(long total, long part) { 555 Checksum sum = new Checksum(total); 556 sum.h -= part >>> SHIFT_2_BYTES; 557 sum.l -= part & MASK_2_BYTES; 558 return sum.getChecksum(); 559 } 560 561 /** 562 * Sets the <code>DATASUM</code> and <code>CHECKSUM</code> keywords in a FITS header, based on the provided checksum 563 * of the data (calculated elsewhere) and the checksum calculated afresh for the header. 564 * 565 * @param header the header in which to store the <code>DATASUM</code> and <code>CHECKSUM</code> values 566 * @param datasum the checksum for the data segment that follows the header in the HDU. 567 * 568 * @throws FitsException if there was an error serializing the header. Note, the method never throws any other type 569 * of exception (including runtime exceptions), which are instead wrapped into a 570 * <code>FitsException</code> when they occur. 571 * 572 * @see #setChecksum(BasicHDU) 573 * @see #getStoredDatasum(Header) 574 * 575 * @since 1.17 576 */ 577 public static void setDatasum(Header header, long datasum) throws FitsException { 578 // Add the freshly calculated datasum to the header, before calculating the checksum 579 header.seekTail(); 580 header.updateLine(DATASUM, 581 new HeaderCard(DATASUM.key(), Long.toString(datasum), "data checksum at " + FitsDate.getFitsDateString())); 582 583 HeaderCard hc = header.getCard(CHECKSUM); 584 585 if (hc != null) { 586 hc.setValue(CHECKSUM_DEFAULT); 587 hc.setComment("HDU checksum at " + FitsDate.getFitsDateString()); 588 } else { 589 header.seekTail(); 590 header.addValue(CHECKSUM, CHECKSUM_DEFAULT); 591 } 592 593 long hsum = checksum(header); 594 header.getCard(CHECKSUM).setValue(encode(sumOf(hsum, datasum))); 595 } 596 597 /** 598 * Computes and sets the DATASUM and CHECKSUM keywords for a given HDU. This method calculates the sums from 599 * scratch, and can be computationally expensive. There are less expensive incremental update methods that can be 600 * used if the HDU already had sums recorded earlier, which need to be updated e.g. because there were modifications 601 * to the header, or (parts of) the data. 602 * 603 * @param hdu the HDU to be updated. 604 * 605 * @throws FitsException if there was an error serializing the HDU. Note, the method never throws any other type of 606 * exception (including runtime exceptions), which are instead wrapped into a 607 * <code>FitsException</code> when they occur. 608 * 609 * @see #setDatasum(Header, long) 610 * @see #sumOf(long...) 611 * @see #differenceOf(long, long) 612 * 613 * @author R J Mather, Attila Kovacs 614 */ 615 public static void setChecksum(BasicHDU<?> hdu) throws FitsException { 616 try { 617 setDatasum(hdu.getHeader(), checksum(hdu.getData())); 618 } catch (FitsException e) { 619 throw e; 620 } catch (Exception e) { 621 throw new FitsException("Exception while computing data checksum: " + e.getMessage(), e); 622 } 623 } 624 625 /** 626 * Returns the DATASUM value stored in a FITS header. 627 * 628 * @param header the FITS header 629 * 630 * @return The stored datasum value (unsigned 32-bit integer) as a Java <code>long</code>. 631 * 632 * @throws FitsException if the header does not contain a <code>DATASUM</code> entry. 633 * 634 * @since 1.17 635 * 636 * @see #setDatasum(Header, long) 637 * @see BasicHDU#getStoredDatasum() 638 */ 639 public static long getStoredDatasum(Header header) throws FitsException { 640 HeaderCard hc = header.getCard(DATASUM); 641 642 if (hc == null) { 643 throw new FitsException("Header does not have a DATASUM value."); 644 } 645 646 return hc.getValue(Long.class, 0L) & FitsIO.INTEGER_MASK; 647 } 648 649 /** 650 * Returns the decoded CHECKSUM value stored in a FITS header. 651 * 652 * @deprecated Not very useful, since it has no meaning other than ensuring that the checksum of the 653 * HDU yields <code>(int) -1</code> (that is <code>0xffffffff</code>) after including 654 * this value for the CHECKSUM keyword in the header. It will be removed in the 655 * future. 656 * 657 * @param header the FITS header 658 * 659 * @return The decoded <code>CHECKSUM</code> value (unsigned 32-bit integer) recorded in the 660 * header as a Java <code>long</code>. 661 * 662 * @throws FitsException if the header does not contain a <code>CHECKSUM</code> entry, or it is invalid. 663 * 664 * @since 1.17 665 * 666 * @see #getStoredDatasum(Header) 667 * @see #setChecksum(BasicHDU) 668 */ 669 public static long getStoredChecksum(Header header) throws FitsException { 670 String encoded = header.getStringValue(CHECKSUM); 671 672 if (encoded == null) { 673 throw new FitsException("Header does not have a CHECKUM value."); 674 } 675 676 return decode(encoded); 677 } 678 }