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