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 @Deprecated
294 public static long checksum(Data data) throws FitsException {
295 return compute(data);
296 }
297
298 /**
299 * Computes the checksum for a FITS header. It returns the checksum for the header as is, without attempting to
300 * validate the header, or modifying it in any way.
301 *
302 * @deprecated Use {@link BasicHDU#verifyIntegrity()} instead.
303 *
304 * @param header The FITS header object for which to calculate a checksum
305 *
306 * @return The checksum of the data
307 *
308 * @throws FitsException If there was an error serializing the FITS header
309 *
310 * @see #checksum(Data)
311 *
312 * @since 1.17
313 */
314 @Deprecated
315 public static long checksum(Header header) throws FitsException {
316 return compute(header);
317 }
318
319 /**
320 * Calculates the FITS checksum for a HDU, e.g to compare agains the value stored under the CHECKSUM header keyword.
321 * The
322 *
323 * @param hdu The Fits HDU for which to calculate a checksum, including both the header and data
324 * segments.
325 *
326 * @return The calculated checksum for the given HDU.
327 *
328 * @throws FitsException if there was an error accessing the contents of the HDU.
329 *
330 * @see BasicHDU#verifyIntegrity()
331 * @see #checksum(Data)
332 * @see #sumOf(long...)
333 *
334 * @deprecated Use {@link BasicHDU#verifyIntegrity()} instead to verify checksums.
335 *
336 * @since 1.17
337 */
338 @Deprecated
339 public static long checksum(BasicHDU<?> hdu) throws FitsException {
340 return sumOf(checksum(hdu.getHeader()), checksum(hdu.getData()));
341 }
342
343 /**
344 * Computes the checksum directly from a region of a random access file, by buffering moderately sized chunks from
345 * the file as necessary. The file may be very large, up to the full range of 64-bit addresses.
346 *
347 * @param f the random access file, from which to compute a checksum
348 * @param from the starting position in the file, where to start computing the checksum from.
349 * @param size the number of bytes in the file to include in the checksum calculation.
350 *
351 * @return the checksum for the given segment of the file
352 *
353 * @throws IOException if there was a problem accessing the file during the computation.
354 *
355 * @since 1.17
356 *
357 * @see #checksum(ByteBuffer)
358 * @see #checksum(Data)
359 */
360 public static long checksum(RandomAccess f, long from, long size) throws IOException {
361 if (f == null) {
362 return 0L;
363 }
364
365 int len = (int) Math.min(BUFFER_SIZE, size);
366 byte[] buf = new byte[len];
367 long oldpos = f.position();
368 f.position(from);
369 long sum = 0;
370
371 while (size > 0) {
372 len = (int) Math.min(BUFFER_SIZE, size);
373 len = f.read(buf, 0, len);
374 sum = sumOf(sum, checksum(buf, 0, len));
375 from += len;
376 size -= len;
377 }
378
379 f.position(oldpos);
380 return sum;
381 }
382
383 /**
384 * @deprecated Use {@link #encode(long, boolean)} instead.
385 *
386 * @param c The calculated 32-bit (unsigned) checksum
387 * @param compl Whether to complement the raw checksum (as defined by the convention).
388 *
389 * @return The encoded checksum, suitably encoded for use with the CHECKSUM header
390 */
391 @Deprecated
392 public static String checksumEnc(final long c, final boolean compl) {
393 return encode(c, compl);
394 }
395
396 /**
397 * Encodes the complemented checksum. It is the same as <code>encode(checksum, true)</code>.
398 *
399 * @param checksum The calculated 32-bit (unsigned) checksum
400 *
401 * @return The encoded checksum, suitably encoded for use with the CHECKSUM header
402 *
403 * @see #decode(String)
404 *
405 * @since 1.17
406 */
407 public static String encode(long checksum) {
408 return encode(checksum, true);
409 }
410
411 /**
412 * Encodes the given checksum as is or by its complement.
413 *
414 * @param checksum The calculated 32-bit (unsigned) checksum
415 * @param compl If <code>true</code> the complement of the specified value will be encoded. Otherwise, the value
416 * as is will be encoded. (FITS normally uses the complemenyed value).
417 *
418 * @return The encoded checksum, suitably encoded for use with the CHECKSUM header
419 *
420 * @see #decode(String, boolean)
421 *
422 * @since 1.17
423 */
424 public static String encode(long checksum, boolean compl) {
425 if (compl) {
426 checksum = ~checksum & FitsIO.INTEGER_MASK;
427 }
428
429 final byte[] asc = new byte[CHECKSUM_STRING_SIZE];
430 final byte[] ch = new byte[CHECKSUM_BLOCK_SIZE];
431 final int sum = (int) checksum;
432
433 for (int i = 0; i < CHECKSUM_BLOCK_SIZE; i++) {
434 // each byte becomes four
435 final int byt = MASK_BYTE & (sum >>> SELECT_BYTE[i]);
436
437 Arrays.fill(ch, (byte) ((byt >>> 2) + ASCII_ZERO)); // quotient
438 ch[0] += (byte) (byt & CHECKSUM_BLOCK_MASK); // remainder
439
440 for (int j = 0; j < CHECKSUM_BLOCK_SIZE; j += 2) {
441 while (EXCLUDE.indexOf(ch[j]) >= 0 || EXCLUDE.indexOf(ch[j + 1]) >= 0) {
442 ch[j]++;
443 ch[j + 1]--;
444 }
445 }
446
447 for (int j = 0; j < CHECKSUM_BLOCK_SIZE; j++) {
448 int k = CHECKSUM_BLOCK_SIZE * j + i + 1;
449 k = (k < CHECKSUM_STRING_SIZE) ? k : 0; // rotate right
450 asc[k] = ch[j];
451 }
452 }
453
454 return new String(asc, StandardCharsets.US_ASCII);
455 }
456
457 /**
458 * Decodes an encoded (and complemented) checksum. The same as <code>decode(encoded, true)</code>, and the the
459 * inverse of {@link #encode(long)}.
460 *
461 * @param encoded The encoded checksum (16 character string)
462 *
463 * @return The unsigned 32-bit integer complemeted checksum.
464 *
465 * @throws IllegalArgumentException if the checksum string is invalid (wrong length or contains illegal ASCII
466 * characters)
467 *
468 * @see #encode(long)
469 *
470 * @since 1.17
471 */
472 public static long decode(String encoded) throws IllegalArgumentException {
473 return decode(encoded, true);
474 }
475
476 /**
477 * Decodes an encoded checksum, complementing it as required. It is the inverse of {@link #encode(long, boolean)}.
478 *
479 * @param encoded the encoded checksum (16 character string)
480 * @param compl whether to complement the checksum after decoding. Normally FITS uses
481 * complemented 32-bit checksums, so typically this optional argument should be
482 * <code>true</code>.
483 *
484 * @return The unsigned 32-bit integer checksum.
485 *
486 * @throws IllegalArgumentException if the checksum string is invalid (wrong length or contains illegal ASCII
487 * characters)
488 *
489 * @see #encode(long, boolean)
490 *
491 * @since 1.17
492 */
493 public static long decode(String encoded, boolean compl) throws IllegalArgumentException {
494 byte[] bytes = encoded.getBytes(StandardCharsets.US_ASCII);
495 if (bytes.length != CHECKSUM_STRING_SIZE) {
496 throw new IllegalArgumentException("Bad checksum with " + bytes.length + " chars (expected 16)");
497 }
498 // Rotate the bytes one to the left
499 byte tmp = bytes[0];
500 System.arraycopy(bytes, 1, bytes, 0, CHECKSUM_STRING_SIZE - 1);
501 bytes[CHECKSUM_STRING_SIZE - 1] = tmp;
502
503 for (int i = 0; i < CHECKSUM_STRING_SIZE; i++) {
504 if (bytes[i] < ASCII_ZERO) {
505 throw new IllegalArgumentException("Bad checksum with illegal char " + Integer.toHexString(bytes[i])
506 + " at pos " + i + " (ASCII below 0x30)");
507 }
508 bytes[i] -= ASCII_ZERO;
509 }
510
511 ByteBuffer bb = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
512
513 long sum = (bb.getInt() + bb.getInt() + bb.getInt() + bb.getInt());
514 return (compl ? ~sum : sum) & FitsIO.INTEGER_MASK;
515 }
516
517 /**
518 * Calculates the total checksum from partial sums. For example combining checksums from a header and data segment
519 * of a HDU, or for composing a data checksum from image tiles.
520 *
521 * @param parts The partial sums that are to be added together.
522 *
523 * @return The aggregated checksum as a 32-bit unsigned value.
524 *
525 * @see #differenceOf(long, long)
526 *
527 * @since 1.17
528 */
529 public static long sumOf(long... parts) {
530 Checksum sum = new Checksum(0);
531
532 for (long part : parts) {
533 sum.h += part >>> SHIFT_2_BYTES;
534 sum.l += part & MASK_2_BYTES;
535 }
536 return sum.getChecksum();
537 }
538
539 /**
540 * Subtracts a partial checksum from an aggregated total. One may use it, for example to update the datasum of a
541 * large data, when modifying only a small segment of it. Thus, one would first subtract the checksum of the old
542 * segment from tha prior datasum, and then add the checksum of the new data segment -- without hacing to
543 * recalculate the checksum for the entire data again.
544 *
545 * @deprecated Not foolproof, because of the carry over handling in checksums, some additionas are not
546 * reversible. E.g. 0xffffffff + 0xffffffff = 0xffffffff, but 0xffffffff - 0xffffffff = 0;
547 *
548 * @param total The total checksum.
549 * @param part The partial checksum to be subtracted from the total.
550 *
551 * @return The checksum after subtracting the partial sum, as a 32-bit unsigned value.
552 *
553 * @see #sumOf(long...)
554 *
555 * @since 1.17
556 */
557 @Deprecated
558 public static long differenceOf(long total, long part) {
559 Checksum sum = new Checksum(total);
560 sum.h -= part >>> SHIFT_2_BYTES;
561 sum.l -= part & MASK_2_BYTES;
562 return sum.getChecksum();
563 }
564
565 /**
566 * Sets the <code>DATASUM</code> and <code>CHECKSUM</code> keywords in a FITS header, based on the provided checksum
567 * of the data (calculated elsewhere) and the checksum calculated afresh for the header.
568 *
569 * @param header the header in which to store the <code>DATASUM</code> and <code>CHECKSUM</code> values
570 * @param datasum the checksum for the data segment that follows the header in the HDU.
571 *
572 * @throws FitsException if there was an error serializing the header. Note, the method never throws any other type
573 * of exception (including runtime exceptions), which are instead wrapped into a
574 * <code>FitsException</code> when they occur.
575 *
576 * @see #setChecksum(BasicHDU)
577 * @see #getStoredDatasum(Header)
578 *
579 * @since 1.17
580 */
581 public static void setDatasum(Header header, long datasum) throws FitsException {
582 // Add the freshly calculated datasum to the header, before calculating the checksum
583 header.seekTail();
584 header.updateLine(DATASUM,
585 new HeaderCard(DATASUM.key(), Long.toString(datasum), "data checksum at " + FitsDate.getFitsDateString()));
586
587 HeaderCard hc = header.getCard(CHECKSUM);
588
589 if (hc != null) {
590 hc.setValue(CHECKSUM_DEFAULT);
591 hc.setComment("HDU checksum at " + FitsDate.getFitsDateString());
592 } else {
593 header.seekTail();
594 header.addValue(CHECKSUM, CHECKSUM_DEFAULT);
595 }
596
597 long hsum = checksum(header);
598 header.getCard(CHECKSUM).setValue(encode(sumOf(hsum, datasum)));
599 }
600
601 /**
602 * Computes and sets the DATASUM and CHECKSUM keywords for a given HDU. This method calculates the sums from
603 * scratch, and can be computationally expensive. There are less expensive incremental update methods that can be
604 * used if the HDU already had sums recorded earlier, which need to be updated e.g. because there were modifications
605 * to the header, or (parts of) the data.
606 *
607 * @param hdu the HDU to be updated.
608 *
609 * @throws FitsException if there was an error serializing the HDU. Note, the method never throws any other type of
610 * exception (including runtime exceptions), which are instead wrapped into a
611 * <code>FitsException</code> when they occur.
612 *
613 * @see #setDatasum(Header, long)
614 * @see #sumOf(long...)
615 * @see #differenceOf(long, long)
616 */
617 public static void setChecksum(BasicHDU<?> hdu) throws FitsException {
618 try {
619 setDatasum(hdu.getHeader(), checksum(hdu.getData()));
620 } catch (FitsException e) {
621 throw e;
622 } catch (Exception e) {
623 throw new FitsException("Exception while computing data checksum: " + e.getMessage(), e);
624 }
625 }
626
627 /**
628 * Returns the DATASUM value stored in a FITS header.
629 *
630 * @param header the FITS header
631 *
632 * @return The stored datasum value (unsigned 32-bit integer) as a Java <code>long</code>.
633 *
634 * @throws FitsException if the header does not contain a <code>DATASUM</code> entry.
635 *
636 * @since 1.17
637 *
638 * @see #setDatasum(Header, long)
639 * @see BasicHDU#getStoredDatasum()
640 */
641 public static long getStoredDatasum(Header header) throws FitsException {
642 HeaderCard hc = header.getCard(DATASUM);
643
644 if (hc == null) {
645 throw new FitsException("Header does not have a DATASUM value.");
646 }
647
648 return hc.getValue(Long.class, 0L) & FitsIO.INTEGER_MASK;
649 }
650
651 /**
652 * Returns the decoded CHECKSUM value stored in a FITS header.
653 *
654 * @deprecated Not very useful, since it has no meaning other than ensuring that the checksum of the
655 * HDU yields <code>(int) -1</code> (that is <code>0xffffffff</code>) after including
656 * this value for the CHECKSUM keyword in the header. It will be removed in the
657 * future.
658 *
659 * @param header the FITS header
660 *
661 * @return The decoded <code>CHECKSUM</code> value (unsigned 32-bit integer) recorded in the
662 * header as a Java <code>long</code>.
663 *
664 * @throws FitsException if the header does not contain a <code>CHECKSUM</code> entry, or it is invalid.
665 *
666 * @since 1.17
667 *
668 * @see #getStoredDatasum(Header)
669 * @see #setChecksum(BasicHDU)
670 */
671 @Deprecated
672 public static long getStoredChecksum(Header header) throws FitsException {
673 String encoded = header.getStringValue(CHECKSUM);
674
675 if (encoded == null) {
676 throw new FitsException("Header does not have a CHECKUM value.");
677 }
678
679 return decode(encoded);
680 }
681 }