libtiff: Even faster TIFFSetDirectory() using IFD list.

From ada85f3663f907b04f80e036f6fd263fab4b3305 Mon Sep 17 00:00:00 2001
From: Su Laus <[EMAIL REDACTED]>
Date: Sun, 26 Mar 2023 14:09:24 +0000
Subject: [PATCH] Even faster TIFFSetDirectory() using IFD list.

---
 libtiff/tif_dir.c        |  89 +++++++----
 libtiff/tif_dir.h        |   4 +
 libtiff/tif_dirread.c    |  85 ++++++++++-
 libtiff/tif_dirwrite.c   |   5 +
 test/test_directory.c    | 310 ++++++++++++++++++++++++++++++++++++++-
 test/test_open_options.c |  22 ++-
 6 files changed, 469 insertions(+), 46 deletions(-)

diff --git a/libtiff/tif_dir.c b/libtiff/tif_dir.c
index 293d6fde..3d57341f 100644
--- a/libtiff/tif_dir.c
+++ b/libtiff/tif_dir.c
@@ -2030,44 +2030,69 @@ int TIFFSetDirectory(TIFF *tif, tdir_t dirn)
     uint64_t nextdiroff;
     tdir_t nextdirnum = 0;
     tdir_t n;
-    /* Fast path when we just advance relative to the current directory:
-     * start at the current dir offset and continue to seek from there.
-     * Check special cases when relative is not allowed:
-     * - jump back from SubIFD or custom directory
-     * - right after TIFFWriteDirectory() jump back to that directory
-     *   using TIFFSetDirectory() */
-    const int relative = (dirn >= tif->tif_curdir) && (tif->tif_diroff != 0) &&
-                         !tif->tif_setdirectory_force_absolute;
-
-    if (relative)
+
+    if (tif->tif_setdirectory_force_absolute)
     {
-        nextdiroff = tif->tif_diroff;
-        dirn -= tif->tif_curdir;
-        nextdirnum = tif->tif_curdir;
+        /* tif_setdirectory_force_absolute=1 will force parsing the main IFD
+         * chain from the beginning, thus IFD directory list needs to be cleared
+         * from possible SubIFD offsets.
+         */
+        _TIFFCleanupIFDOffsetAndNumberMaps(tif); /* invalidate IFD loop lists */
+    }
+
+    /* Even faster path, if offset is available within IFD loop hash list. */
+    if (!tif->tif_setdirectory_force_absolute &&
+        _TIFFGetOffsetFromDirNumber(tif, dirn, &nextdiroff))
+    {
+        /* Set parameters for following TIFFReadDirectory() below. */
+        tif->tif_nextdiroff = nextdiroff;
+        tif->tif_curdir = dirn;
+        /* Reset to relative stepping */
+        tif->tif_setdirectory_force_absolute = FALSE;
     }
-    else if (!(tif->tif_flags & TIFF_BIGTIFF))
-        nextdiroff = tif->tif_header.classic.tiff_diroff;
     else
-        nextdiroff = tif->tif_header.big.tiff_diroff;
+    {
 
-    /* Reset to relative stepping */
-    tif->tif_setdirectory_force_absolute = FALSE;
+        /* Fast path when we just advance relative to the current directory:
+         * start at the current dir offset and continue to seek from there.
+         * Check special cases when relative is not allowed:
+         * - jump back from SubIFD or custom directory
+         * - right after TIFFWriteDirectory() jump back to that directory
+         *   using TIFFSetDirectory() */
+        const int relative = (dirn >= tif->tif_curdir) &&
+                             (tif->tif_diroff != 0) &&
+                             !tif->tif_setdirectory_force_absolute;
+
+        if (relative)
+        {
+            nextdiroff = tif->tif_diroff;
+            dirn -= tif->tif_curdir;
+            nextdirnum = tif->tif_curdir;
+        }
+        else if (!(tif->tif_flags & TIFF_BIGTIFF))
+            nextdiroff = tif->tif_header.classic.tiff_diroff;
+        else
+            nextdiroff = tif->tif_header.big.tiff_diroff;
 
-    for (n = dirn; n > 0 && nextdiroff != 0; n--)
-        if (!TIFFAdvanceDirectory(tif, &nextdiroff, NULL, &nextdirnum))
+        /* Reset to relative stepping */
+        tif->tif_setdirectory_force_absolute = FALSE;
+
+        for (n = dirn; n > 0 && nextdiroff != 0; n--)
+            if (!TIFFAdvanceDirectory(tif, &nextdiroff, NULL, &nextdirnum))
+                return (0);
+        /* If the n-th directory could not be reached (does not exist),
+         * return here without touching anything further. */
+        if (nextdiroff == 0 || n > 0)
             return (0);
-    /* If the n-th directory could not be reached (does not exist),
-     * return here without touching anything further. */
-    if (nextdiroff == 0 || n > 0)
-        return (0);
 
-    tif->tif_nextdiroff = nextdiroff;
+        tif->tif_nextdiroff = nextdiroff;
 
-    /* Set curdir to the actual directory index. */
-    if (relative)
-        tif->tif_curdir += dirn - n;
-    else
-        tif->tif_curdir = dirn - n;
+        /* Set curdir to the actual directory index. */
+        if (relative)
+            tif->tif_curdir += dirn - n;
+        else
+            tif->tif_curdir = dirn - n;
+    }
 
     /* The -1 decrement is because TIFFReadDirectory will increment
      * tif_curdir after successfully reading the directory. */
@@ -2118,8 +2143,8 @@ int TIFFSetSubDirectory(TIFF *tif, uint64_t diroff)
     tif->tif_nextdiroff = diroff;
     retval = TIFFReadDirectory(tif);
     /* If failed, curdir was not incremented in TIFFReadDirectory(), so set it
-     * back. */
-    if (!retval)
+     * back, but leave it for diroff==0. */
+    if (!retval && diroff != 0)
     {
         if (tif->tif_curdir == TIFF_NON_EXISTENT_DIR_NUMBER)
             tif->tif_curdir = 0;
diff --git a/libtiff/tif_dir.h b/libtiff/tif_dir.h
index fad1eb02..53473fb3 100644
--- a/libtiff/tif_dir.h
+++ b/libtiff/tif_dir.h
@@ -329,6 +329,10 @@ extern "C"
                                             uint64_t diroff);
     extern int _TIFFGetDirNumberFromOffset(TIFF *tif, uint64_t diroff,
                                            tdir_t *dirn);
+    extern int _TIFFGetOffsetFromDirNumber(TIFF *tif, tdir_t dirn,
+                                           uint64_t *diroff);
+    extern int _TIFFRemoveEntryFromDirectoryListByOffset(TIFF *tif,
+                                                         uint64_t diroff);
 
 #if defined(__cplusplus)
 }
diff --git a/libtiff/tif_dirread.c b/libtiff/tif_dirread.c
index d4cc11d0..8c7b4d83 100644
--- a/libtiff/tif_dirread.c
+++ b/libtiff/tif_dirread.c
@@ -4068,11 +4068,10 @@ int TIFFReadDirectory(TIFF *tif)
     if (tif->tif_nextdiroff == 0)
     {
         /* In this special case, tif_diroff needs also to be set to 0.
-         * Furthermore, TIFFSetDirectory() needs to be switched to
-         * absolute stepping. */
+         * This is behind the last IFD, thus no checking or reading necessary.
+         */
         tif->tif_diroff = tif->tif_nextdiroff;
-        tif->tif_setdirectory_force_absolute = TRUE;
-        return 0; /* last offset, thus no checking necessary */
+        return 0;
     }
 
     nextdiroff = tif->tif_nextdiroff;
@@ -5521,6 +5520,7 @@ int _TIFFGetDirNumberFromOffset(TIFF *tif, uint64_t diroff, tdir_t *dirn)
         return 1;
     }
 
+    /* This updates the directory list for all main-IFDs in the file. */
     TIFFNumberOfDirectories(tif);
 
     foundEntry = (TIFFOffsetAndDirNumber *)TIFFHashSetLookup(
@@ -5534,6 +5534,83 @@ int _TIFFGetDirNumberFromOffset(TIFF *tif, uint64_t diroff, tdir_t *dirn)
     return 0;
 } /*--- _TIFFGetDirNumberFromOffset() ---*/
 
+/*
+ * Retrieve the matching IFD directory offset of a given IFD number
+ * from the list of directories already seen.
+ * Returns 1 if the offset was in the list of already seen IFDs and the
+ * directory offset can be returned. The directory list is not updated.
+ * Otherwise returns 0 or if an error occurred.
+ */
+int _TIFFGetOffsetFromDirNumber(TIFF *tif, tdir_t dirn, uint64_t *diroff)
+{
+
+    if (tif->tif_map_dir_number_to_offset == NULL)
+        return 0;
+    TIFFOffsetAndDirNumber entry;
+    entry.offset = 0; /* not used */
+    entry.dirNumber = dirn;
+
+    TIFFOffsetAndDirNumber *foundEntry =
+        (TIFFOffsetAndDirNumber *)TIFFHashSetLookup(
+            tif->tif_map_dir_number_to_offset, &entry);
+    if (foundEntry)
+    {
+        *diroff = foundEntry->offset;
+        return 1;
+    }
+
+    return 0;
+} /*--- _TIFFGetOffsetFromDirNumber() ---*/
+
+/*
+ * Remove an entry from the directory list of already seen directories
+ * by directory offset.
+ * If an entry is to be removed from the list, it is also okay if the entry
+ * is not in the list or the list does not exist.
+ */
+int _TIFFRemoveEntryFromDirectoryListByOffset(TIFF *tif, uint64_t diroff)
+{
+    if (tif->tif_map_dir_offset_to_number == NULL)
+        return 1;
+
+    TIFFOffsetAndDirNumber entryOld;
+    entryOld.offset = diroff;
+    entryOld.dirNumber = 0;
+    /* We must remove first from tif_map_dir_number_to_offset as the
+     * entry is owned (and thus freed) by tif_map_dir_offset_to_number.
+     * However, we need firstly to find the directory number from offset. */
+
+    TIFFOffsetAndDirNumber *foundEntryOldOff =
+        (TIFFOffsetAndDirNumber *)TIFFHashSetLookup(
+            tif->tif_map_dir_offset_to_number, &entryOld);
+    if (foundEntryOldOff)
+    {
+        entryOld.dirNumber = foundEntryOldOff->dirNumber;
+        if (tif->tif_map_dir_number_to_offset != NULL)
+        {
+            TIFFOffsetAndDirNumber *foundEntryOldDir =
+                (TIFFOffsetAndDirNumber *)TIFFHashSetLookup(
+                    tif->tif_map_dir_number_to_offset, &entryOld);
+            if (foundEntryOldDir)
+            {
+                TIFFHashSetRemove(tif->tif_map_dir_number_to_offset,
+                                  foundEntryOldDir);
+                TIFFHashSetRemove(tif->tif_map_dir_offset_to_number,
+                                  foundEntryOldOff);
+                return 1;
+            }
+        }
+        else
+        {
+            TIFFErrorExtR(tif, "_TIFFRemoveEntryFromDirectoryListByOffset",
+                          "Unexpectedly tif_map_dir_number_to_offset is "
+                          "missing but tif_map_dir_offset_to_number exists.");
+            return 0;
+        }
+    }
+    return 1;
+} /*--- _TIFFRemoveEntryFromDirectoryListByOffset() ---*/
+
 /*
  * Check the count field of a directory entry against a known value.  The
  * caller is expected to skip/ignore the tag if there is a mismatch.
diff --git a/libtiff/tif_dirwrite.c b/libtiff/tif_dirwrite.c
index 717afbac..a6a485f8 100644
--- a/libtiff/tif_dirwrite.c
+++ b/libtiff/tif_dirwrite.c
@@ -320,6 +320,7 @@ int TIFFRewriteDirectory(TIFF *tif)
      * Find and zero the pointer to this directory, so that TIFFLinkDirectory
      * will cause it to be added after this directories current pre-link.
      */
+    uint64_t torewritediroff = tif->tif_diroff;
 
     if (!(tif->tif_flags & TIFF_BIGTIFF))
     {
@@ -387,6 +388,8 @@ int TIFFRewriteDirectory(TIFF *tif)
                 nextdir = nextnextdir;
             }
         }
+        /* Remove skipped offset from IFD loop directory list. */
+        _TIFFRemoveEntryFromDirectoryListByOffset(tif, torewritediroff);
     }
     else
     {
@@ -456,6 +459,8 @@ int TIFFRewriteDirectory(TIFF *tif)
                 nextdir = nextnextdir;
             }
         }
+        /* Remove skipped offset from IFD loop directory list. */
+        _TIFFRemoveEntryFromDirectoryListByOffset(tif, torewritediroff);
     }
 
     /*
diff --git a/test/test_directory.c b/test/test_directory.c
index 1cd14274..6cc3baf8 100644
--- a/test/test_directory.c
+++ b/test/test_directory.c
@@ -381,6 +381,68 @@ int test_arbitrary_directrory_loading(bool is_big_tiff)
         goto failure;
     }
 
+    /* Test very fast  TIFFSetDirectory() using IFD loop directory list.
+     * First populate IFD loop directory list and then go through directories in
+     * reverse order. Within between read after end of IFDs using
+     * TIFFReadDirectory() where IFD loop directory list should be kept. */
+    for (int i = 0; i < N_DIRECTORIES; i++)
+    {
+        if (!TIFFSetDirectory(tif, i))
+        {
+            fprintf(stderr, "Can't set %d.th directory from %s\n", i, filename);
+            goto failure;
+        }
+    }
+    TIFFReadDirectory(tif);
+    for (int i = N_DIRECTORIES - 1; i >= 0; i--)
+    {
+        if (!TIFFSetDirectory(tif, i))
+        {
+            fprintf(stderr, "Can't set %d.th directory from %s\n", i, filename);
+            goto failure;
+        }
+        if (!is_requested_directory(tif, i, filename))
+        {
+            goto failure;
+        }
+    }
+
+    /* Test not existing directory number */
+    if (TIFFSetDirectory(tif, N_DIRECTORIES))
+    {
+        fprintf(stderr,
+                "No expected fail for accessing not existant directory number "
+                "%d in file %s\n",
+                N_DIRECTORIES, filename);
+        goto failure;
+    }
+
+    /* Close and Reopen prepared testfile */
+    TIFFClose(tif);
+    tif = TIFFOpen(filename, "r+");
+    if (!tif)
+    {
+        fprintf(stderr, "Can't create %s\n", filename);
+        return 1;
+    }
+
+    /* Step through directories just using TIFFSetSubDirectory() */
+    for (int i = N_DIRECTORIES - 1; i >= 0; i--)
+    {
+        if (!TIFFSetSubDirectory(tif, offsets_base[i]))
+        {
+            fprintf(stderr, "Can't set %d.th directory from %s\n", i, filename);
+            goto failure;
+        }
+        if (!is_requested_directory(tif, i, filename))
+        {
+            goto failure;
+        }
+    }
+
+    /* More specialized test cases for relative seeking within TIFFSetDirectory.
+     * However, with using IFD loop directory list, most of this test cases will
+     * never be reached! */
     if (!TIFFSetDirectory(tif, 2))
     {
         fprintf(stderr, "Can't set directory %d within %s\n", 2, filename);
@@ -614,6 +676,165 @@ int test_arbitrary_directrory_loading(bool is_big_tiff)
     return 1;
 }
 
+/* Tests SubIFD writing and reading
+ *
+ *
+ */
+int test_SubIFD_directrory_handling(bool is_big_tiff)
+{
+    const char *filename = "test_SubIFD_directrory_handling.tif";
+
+/* Define the number of sub-IFDs you are going to write */
+#define NUMBER_OF_SUBIFDs 3
+    uint16_t number_of_sub_IFDs = NUMBER_OF_SUBIFDs;
+    toff_t sub_IFDs_offsets[NUMBER_OF_SUBIFDs] = {
+        0UL}; /* array for SubIFD tag */
+    int blnWriteSubIFD = 0;
+    int i;
+    int iIFD = 0, iSubIFD = 0;
+    TIFF *tif;
+    int expected_original_dirnumber;
+
+    /* Create a file and write N_DIRECTORIES (10) directories to it */
+    tif = TIFFOpen(filename, is_big_tiff ? "w8" : "w");
+    if (!tif)
+    {
+        fprintf(stderr, "Can't create %s\n", filename);
+        return 1;
+    }
+    for (i = 0; i < N_DIRECTORIES; i++)
+    {
+        if (write_data_to_current_directory(
+                tif, blnWriteSubIFD ? 200 + iSubIFD++ : iIFD++))
+        {
+            fprintf(stderr, "Can't write data to current directory in %s\n",
+                    filename);
+            goto failure;
+        }
+        if (blnWriteSubIFD)
+        {
+            /* SUBFILETYPE tag is not mandatory for SubIFD writing, but a
+             * good idea to indicate thumbnails */
+            if (!TIFFSetField(tif, TIFFTAG_SUBFILETYPE, FILETYPE_REDUCEDIMAGE))
+                goto failure;
+        }
+
+        /* For the second multi-page image, trigger TIFFWriteDirectory() to
+         * switch for the next number_of_sub_IFDs calls to add those as SubIFDs
+         * e.g. for thumbnails */
+        if (1 == i)
+        {
+            blnWriteSubIFD = 1;
+            /* Now here is the trick: the next n directories written
+             * will be sub-IFDs of the main-IFD (where n is number_of_sub_IFDs
+             * specified when you set the TIFFTAG_SUBIFD field.
+             * The SubIFD offset array sub_IFDs_offsets is filled automatically
+             * with the proper offset values by the following number_of_sub_IFDs
+             * TIFFWriteDirectory() calls and are updated in the related
+             * main-IFD with the last call.
+             */
+            if (!TIFFSetField(tif, TIFFTAG_SUBIFD, number_of_sub_IFDs,
+                              sub_IFDs_offsets))
+                goto failure;
+        }
+
+        if (!TIFFWriteDirectory(tif))
+        {
+            fprintf(stderr, "Can't write directory to %s\n", filename);
+            goto failure;
+        }
+
+        if (iSubIFD >= number_of_sub_IFDs)
+            blnWriteSubIFD = 0;
+    }
+    TIFFClose(tif);
+
+    /* Reopen prepared testfile */
+    tif = TIFFOpen(filename, "r+");
+    if (!tif)
+    {
+        fprintf(stderr, "Can't create %s\n", filename);
+        goto failure;
+    }
+
+    tdir_t numberOfMainIFDs = TIFFNumberOfDirectories(tif);
+    if (numberOfMainIFDs != N_DIRECTORIES - number_of_sub_IFDs)
+    {
+        fprintf(stderr,
+                "Unexpected number of directories in %s. Expected %i, "
+                "found %i.\n",
+                filename, N_DIRECTORIES - number_of_sub_IFDs, numberOfMainIFDs);
+        goto failure;
+    }
+
+    tdir_t currentDirNumber = TIFFCurrentDirectory(tif);
+
+    /* The first directory is already read through TIFFOpen() */
+    int blnRead = 0;
+    expected_original_dirnumber = 1;
+    do
+    {
+        /* Check if there are SubIFD subfiles */
+        void *ptr;
+        if (TIFFGetField(tif, TIFFTAG_SUBIFD, &number_of_sub_IFDs, &ptr))
+        {
+            /* Copy SubIFD array from pointer */
+            memcpy(sub_IFDs_offsets, ptr,
+                   number_of_sub_IFDs * sizeof(sub_IFDs_offsets[0]));
+
+            for (i = 0; i < number_of_sub_IFDs; i++)
+            {
+                /* Read first SubIFD directory */
+                if (!TIFFSetSubDirectory(tif, sub_IFDs_offsets[i]))
+                    goto failure;
+                if (!is_requested_directory(tif, 200 + i, filename))
+                {
+                    goto failure;
+                }
+                /* Check if there is a SubIFD chain behind the first one from
+                 * the array, as specified by Adobe */
+                int n = 0;
+                while (TIFFReadDirectory(tif))
+                {
+                    /* analyse subfile */
+                    if (!is_requested_directory(tif, 201 + i + n++, filename))
+                        goto failure;
+                }
+            }
+            /* Go back to main-IFD chain and re-read that main-IFD directory */
+            if (!TIFFSetDirectory(tif, currentDirNumber))
+                goto failure;
+        }
+        /* Read next main-IFD directory (subfile) */
+        blnRead = TIFFReadDirectory(tif);
+        currentDirNumber = TIFFCurrentDirectory(tif);
+        if (blnRead && !is_requested_directory(
+                           tif, expected_original_dirnumber++, filename))
+            goto failure;
+    } while (blnRead);
+
+    /*--- Now test arbitrary directory loading with SubIFDs ---*/
+    if (!TIFFSetSubDirectory(tif, sub_IFDs_offsets[1]))
+        goto failure;
+    if (!is_requested_directory(tif, 201, filename))
+    {
+        goto failure;
+    }
+
+    TIFFClose(tif);
+    unlink(filename);
+    return 0;
+
+failure:
+    if (tif)
+    {
+        TIFFClose(tif);
+        tif = NULL;
+    }
+    unlink(filename);
+    return 1;
+} /*--- test_SubIFD_directrory_handling() ---*/
+
 /* Checks that rewriting a directory does not break the directory linked
  * list
  *
@@ -623,6 +844,8 @@ int test_arbitrary_directrory_loading(bool is_big_tiff)
  * skipped, otherwise the linked list will be broken at the point where it
  * connected to the rewritten directory, resulting in the loss of the
  * directories that come after it.
+ * Rewriting the first directory requires an additional test, because it is
+ * treated differently from the directories that have a predecessor in the list.
  */
 int test_rewrite_lastdir_offset(bool is_big_tiff)
 {
@@ -652,17 +875,22 @@ int test_rewrite_lastdir_offset(bool is_big_tiff)
         }
     }
 
-    /* Without closing it, go to the fifth directory */
+    /* Without closing the file, go to the fifth directory
+     * and check, if dirn is requested directory. */
     TIFFSetDirectory(tif, 4);
-
-    /* Check, if dirn is requested directory */
     if (!is_requested_directory(tif, 4, filename))
     {
         TIFFClose(tif);
         return 4;
     }
 
-    /* Rewrite the fifth directory by calling TIFFRewriteDirectory */
+    /* Rewrite the fifth directory by calling TIFFRewriteDirectory
+     * and check, if the offset of IFD loaded by TIFFSetDirectory() is
+     * different. Then, the IFD loop directory list is correctly maintained for
+     * speed up of TIFFSetDirectory() with directly getting the offset that
+     * list.
+     */
+    uint64_t off1 = TIFFCurrentDirOffset(tif);
     if (write_data_to_current_directory(tif, 4))
     {
         fprintf(stderr, "Can't write data to fifth directory in %s\n",
@@ -674,6 +902,64 @@ int test_rewrite_lastdir_offset(bool is_big_tiff)
         fprintf(stderr, "Can't rewrite fifth directory to %s\n", filename);
         goto failure;
     }
+    i = 4;
+    if (!TIFFSetDirectory(tif, i))
+    {
+        fprintf(stderr, "Can't set %d.th directory from %s\n", i, filename);
+        goto failure;
+    }
+    uint64_t off2 = TIFFCurrentDirOffset(tif);
+    if (!is_requested_directory(tif, i, filename))
+    {
+        goto failure;
+    }
+    if (off1 == off2)
+    {
+        fprintf(stderr,
+                "Rewritten directory %d was not correctly accessed by "
+                "TIFFSetDirectory() in file %s\n",
+                i, filename);
+        goto failure;
+    }
+
+    /* Now, perform the test for the first directory */
+    TIFFSetDirectory(tif, 0);
+    if (!is_requested_directory(tif, 0, filename))
+    {
+        TIFFClose(tif);
+        return 5;
+    }
+    off1 = TIFFCurrentDirOffset(tif);
+    if (write_data_to_current_directory(tif, 0))
+    {
+        fprintf(stderr, "Can't write data to first directory in %s\n",
+                filename);
+        goto failure;
+    }
+    if (!TIFFRewriteDirectory(tif))
+    {
+        fprintf(stderr, "Can't rewrite first directory to %s\n", filename);
+        goto failure;
+    }
+    i = 0;
+    if (!TIFFSetDirectory(tif, i))
+    {
+        fprintf(stderr, "Can't set %d.th directory from %s\n", i, filename);
+        goto failure;
+    }
+    off2 = TIFFCurrentDirOffset(tif);
+    if (!is_requested_directory(tif, i, filename))
+    {
+        goto failure;
+    }
+    if (off1 == off2)
+    {
+        fprintf(stderr,
+                "Rewritten directory %d was not correctly accessed by "
+                "TIFFSetDirectory() in file %s\n",
+                i, filename);
+        goto failure;
+    }
 
     TIFFClose(tif);
     tif = NULL;
@@ -866,7 +1152,7 @@ int main()
         return 1;
     }
 
-    /* Finally test arbitrary directory loading */
+    /* Test arbitrary directory loading */
     if (test_arbitrary_directrory_loading(false))
     {
         fprintf(stderr,
@@ -880,5 +1166,19 @@ int main()
         return 1;
     }
 
+    /* Test SubIFD writing and reading */
+    if (test_SubIFD_directrory_handling(false))
+    {
+        fprintf(stderr,
+                "Failed during non-BigTIFF SubIFD_directrory_handling test.\n");
+        return 1;
+    }
+    if (test_SubIFD_directrory_handling(true))
+    {
+        fprintf(stderr,
+                "Failed during BigTIFF SubIFD_directrory_handling test.\n");
+        return 1;
+    }
+
     return 0;
 }
diff --git a/test/test_open_options.c b/test/test_open_options.c
index 136adb37..e7608a19 100644
--- a/test/test_open_options.c
+++ b/test/test_open_options.c
@@ -158,13 +158,14 @@ static int test_error_handler()
 
 static int test_TIFFOpenOptionsSetMaxSingleMemAlloc(
     tmsize_t limit, int expected_to_fail_in_open,
-    int expected_to_fail_in_write_directory)
+    int expected_to_fail_in_write_directory, bool is_big_tiff)
 {
     int ret = 0;
     TIFFOpenOptions *opts = TIFFOpenOptionsAlloc();
     assert(opts);
     TIFFOpenOptionsSetMaxSingleMemAlloc(opts, limit);
-    TIFF *tif = TIFFOpenExt("test_error_handler.tif", "w", opts);
+    TIFF *tif =
+        TIFFOpenExt("test_error_handler.tif", is_big_tiff ? "w8" : "w", opts);
     TIFFOpenOptionsFree(opts);
     if (expected_to_fail_in_open)
     {
@@ -217,10 +218,21 @@ int main()
 {
     int ret = 0;
     ret += test_error_handler();
-    ret += test_TIFFOpenOptionsSetMaxSingleMemAlloc(1, TRUE, -1);
+    fprintf(stderr, "---- test_TIFFOpenOptionsSetMaxSingleMemAlloc "
+                    "with non-BigTIFF ---- \n");
+    ret += test_TIFFOpenOptionsSetMaxSingleMemAlloc(1, TRUE, -1, FALSE);
+    ret += test_TIFFOpenOptionsSetMaxSingleMemAlloc(
+        sizeof(TIFF) + strlen("test_error_handler.tif") + 1, FALSE, TRUE,
+        FALSE);
+    ret += test_TIFFOpenOptionsSetMaxSingleMemAlloc(
+        VALUE_SAMPLESPERPIXEL * sizeof(short), FALSE, FALSE, FALSE);
+
+    fprintf(stderr, "\n---- test_TIFFOpenOptionsSetMaxSingleMemAlloc "
+                    "with BigTIFF ---- \n");
+    ret += test_TIFFOpenOptionsSetMaxSingleMemAlloc(1, TRUE, -1, TRUE);
     ret += test_TIFFOpenOptionsSetMaxSingleMemAlloc(
-        sizeof(TIFF) + strlen("test_error_handler.tif") + 1, FALSE, TRUE);
+        sizeof(TIFF) + strlen("test_error_handler.tif") + 1, FALSE, TRUE, TRUE);
     ret += test_TIFFOpenOptionsSetMaxSingleMemAlloc(
-        VALUE_SAMPLESPERPIXEL * sizeof(short), FALSE, FALSE);
+        VALUE_SAMPLESPERPIXEL * sizeof(short), FALSE, FALSE, TRUE);
     return ret;
 }