View Javadoc
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 }