from testing.test_crypto import HeaderTest, AESGCMTest
 from testing.test_multivol import MultivolGnuFormatTest, MultivolPaxFormatTest
 from testing.test_concat_compress import ConcatCompressTest
-from testing.test_recover import RecoverTest, RecoverGZTest, RecoverGZAESTest
+from testing.test_recover import \
+      RecoverCorruptPayloadTest \
+    , RecoverCorruptPayloadGZTest \
+    , RecoverCorruptPayloadGZAESTest \
+    , RecoverCorruptHeaderGZTest \
+    , RecoverCorruptHeaderGZAESTest
 from testing.test_rescue_tar import RescueTarTest
 from testing.test_encryption import EncryptionTest
 from testing.test_deltatar import (DeltaTarTest, DeltaTar2Test,
                          , DeltaTarAes128ConcatTest
                          , HeaderTest, AESGCMTest
                          # testing.test_recover
-                         , RecoverTest, RecoverGZTest, RecoverGZAESTest
+                         , RecoverCorruptPayloadTest
+                         , RecoverCorruptPayloadGZTest
+                         , RecoverCorruptPayloadGZAESTest
+                         , RecoverCorruptHeaderGZTest
+                         , RecoverCorruptHeaderGZAESTest
                          ]:
                 try:
                     t = group (n)
 
     return True
 
 
+def corrupt_header (_, fname, compress, encrypt):
+    """
+    Modify a significant byte in the object header of the format.
+    """
+    if encrypt is True: # damage GCM tag
+        flip_bits (fname, crypto.HDR_OFF_TAG + 1)
+    elif compress is True: # invalidate magic
+        flip_bits (fname, 1)
+    else: # Fudge checksum. From tar(5):
+        #
+        #       struct header_gnu_tar {
+        #               char name[100];
+        #               char mode[8];
+        #               char uid[8];
+        #               char gid[8];
+        #               char size[12];
+        #               char mtime[12];
+        #               char checksum[8];
+        #               …
+        flip_bits (fname, 100 + 8 + 8 + 8 + 12 + 12 + 1)
+
+
+def corrupt_payload_start (_, fname, compress, encrypt):
+    """
+    Modify the byte following the object header structure of the format.
+    """
+    if encrypt is True:
+        flip_bits (fname, crypto.PDTCRYPT_HDR_SIZE + 1)
+    elif compress is True:
+        flip_bits (fname, gz_header_size (fname) + 1)
+    else:
+        flip_bits (fname, tarfile.BLOCKSIZE + 1)
+
+
+
 ###############################################################################
 ## tests                                                                     ##
 ###############################################################################
     COMPRESSION = None
     PASSWORD    = None
     FAILURES    = 0
+    CORRUPT     = corrupt_payload_start
 
 
     def setUp(self):
         shutil.rmtree (self.dst_path)
         shutil.rmtree (self.src_path)
 
-        if self.PASSWORD is not None:
-            flip_bits (backup_full, crypto.PDTCRYPT_HDR_SIZE + 1)
-        elif self.COMPRESSION is not None:
-            flip_bits (backup_full, gz_header_size (backup_full) + 1)
-        else:
-            flip_bits (backup_full, tarfile.BLOCKSIZE + 1)
+        self.CORRUPT (backup_full,
+                      self.COMPRESSION is not None,
+                      self.PASSWORD    is not None)
 
         # normal restore must fail
         try:
         except tarfile.CompressionError:
             if self.PASSWORD is not None or self.COMPRESSION is not None:
                 pass
+            else:
+                raise
         except tarfile.ReadError:
-            if self.PASSWORD is not None or self.COMPRESSION is not None:
+            # can happen with all three modes
+            pass
+        except tarfile.DecryptionError:
+            if self.PASSWORD is not None:
                 pass
+            else:
+                raise
 
         os.chdir (self.pwd) # not restored due to the error above
         # but recover will succeed
                                          "%s/%s" % (bak_path, index_file)
                                      ])
 
+        print ("¤¤¤ failed", failed)
         assert len (failed) == self.FAILURES
 
         # with one file missing
         shutil.rmtree (self.dst_path)
 
 
-class RecoverGZTest (RecoverTest):
+class RecoverCorruptPayloadTest (RecoverTest):
+    COMPRESSION = None
+    PASSWORD    = None
+    FAILURES    = 0
+
+
+class RecoverCorruptPayloadGZTest (RecoverTest):
+    COMPRESSION = "#gz"
+    PASSWORD    = None
+    FAILURES    = 1
+
+
+class RecoverCorruptPayloadGZAESTest (RecoverTest):
+    COMPRESSION = "#gz"
+    PASSWORD    = TEST_PASSWORD
+    FAILURES    = 1
+
+
+class RecoverCorruptHeaderGZTest (RecoverTest):
     COMPRESSION = "#gz"
     PASSWORD    = None
     FAILURES    = 1
+    CORRUPT     = corrupt_header
 
 
-class RecoverGZAESTest (RecoverTest):
+class RecoverCorruptHeaderGZAESTest (RecoverTest):
     COMPRESSION = "#gz"
     PASSWORD    = TEST_PASSWORD
     FAILURES    = 1
+    CORRUPT     = corrupt_header