8 ex__tiff_8c
gullradriel edited this page 2026-10-29 22:06:20 +01:00

examples/ex_tiff.c

Functions

Name
void test_sub_ifd(int be)
Run the checks, or list a file given as the argument.
void test_order(int be)
Parse a built buffer and check every value.
void test_broken(void )
Broken buffers are refused, never read out of bounds.
void put_entry(unsigned char * e, uint16_t tag, uint16_t type, uint32_t count, uint32_t value, int be)
Write one directory entry.
void put32(unsigned char * p, uint32_t v, int be)
Write a 32 bit value in a byte order.
void put16(unsigned char * p, uint16_t v, int be)
Write a 16 bit value in a byte order.
int main(int argc, char ** argv)
int list_file(const char * path)
List the tags of a file's first directory.
void check(const char * what, int ok)
Count and print a failed condition.
size_t build(unsigned char * buf, int be)
Build a DCP shaped buffer: a name, a short, three signed rationals, two floats, and one entry pointing past the end.

Attributes

Name
int failures
failures counted by check()

Functions Documentation

function test_sub_ifd

static void test_sub_ifd(
    int be
)

Run the checks, or list a file given as the argument.

Parameters:

  • argc argument count
  • argv arguments
  • be non-zero for big endian

Return: 0 on success

-V LEVEL sets the log level, as for every example: run_tests.sh passes it, and before this it was taken for the name of a file to list.

A directory pointing to an EXIF sub-directory, read through n_tiff_parse_ifd(): the shape of a camera file's SubSecTimeOriginal.

function test_order

static void test_order(
    int be
)

Parse a built buffer and check every value.

Parameters:

  • be non-zero for big endian

function test_broken

static void test_broken(
    void 
)

Broken buffers are refused, never read out of bounds.

function put_entry

static void put_entry(
    unsigned char * e,
    uint16_t tag,
    uint16_t type,
    uint32_t count,
    uint32_t value,
    int be
)

Write one directory entry.

Parameters:

  • e where
  • tag tag
  • type field type
  • count value count
  • value the value itself, or the offset of the values
  • be non-zero for big endian

function put32

static void put32(
    unsigned char * p,
    uint32_t v,
    int be
)

Write a 32 bit value in a byte order.

Parameters:

  • p where
  • v the value
  • be non-zero for big endian

function put16

static void put16(
    unsigned char * p,
    uint16_t v,
    int be
)

Write a 16 bit value in a byte order.

Parameters:

  • p where
  • v the value
  • be non-zero for big endian

function main

int main(
    int argc,
    char ** argv
)

function list_file

static int list_file(
    const char * path
)

List the tags of a file's first directory.

Parameters:

  • path the file

Return: 0, or 1 when it cannot be read or parsed

function check

static void check(
    const char * what,
    int ok
)

Count and print a failed condition.

Parameters:

  • what what was checked
  • ok the condition

function build

static size_t build(
    unsigned char * buf,
    int be
)

Build a DCP shaped buffer: a name, a short, three signed rationals, two floats, and one entry pointing past the end.

Parameters:

  • buf receives it, 256 bytes
  • be non-zero for big endian

Return: its length

Attributes Documentation

variable failures

static int failures = 0;

failures counted by check()

Source code

/*
 * Nilorea Library
 * Copyright (C) 2005-2026 Castagnier Mickael
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 */


#include "nilorea/n_common.h"
#include "nilorea/n_files.h"
#include "nilorea/n_log.h"
#include "nilorea/n_tiff.h"

#include <math.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

static int failures = 0;

static void check(const char* what, int ok) {
    if (!ok) {
        failures++;
        fprintf(stderr, "FAIL %s\n", what);
    }
}

static void put16(unsigned char* p, uint16_t v, int be) {
    p[be ? 0 : 1] = (unsigned char)(v >> 8);
    p[be ? 1 : 0] = (unsigned char)(v & 0xff);
}

static void put32(unsigned char* p, uint32_t v, int be) {
    int i = 0;

    for (i = 0; i < 4; i++)
        p[be ? i : 3 - i] = (unsigned char)(v >> (24 - 8 * i));
}

static void put_entry(unsigned char* e, uint16_t tag, uint16_t type, uint32_t count, uint32_t value, int be) {
    put16(e, tag, be);
    put16(e + 2, type, be);
    put32(e + 4, count, be);
    put32(e + 8, value, be);
}

static size_t build(unsigned char* buf, int be) {
    const float floats[2] = {1.5f, -0.25f};
    int i = 0;

    memset(buf, 0, 256);
    buf[0] = buf[1] = (unsigned char)(be ? 'M' : 'I');
    put16(buf + 2, N_TIFF_MAGIC_DCP, be);
    put32(buf + 4, 8, be);
    put16(buf + 8, 5, be);
    /* five entries of 12 bytes from 10, values from 80 */
    put_entry(buf + 10, 50708, 2, 12, 80, be); /* ASCII, at 80 */
    put_entry(buf + 22, 50778, 3, 1, 0, be);   /* SHORT, in the entry */
    put16(buf + 22 + 8, 21, be);
    put_entry(buf + 34, 50721, 10, 3, 96, be);   /* SRATIONAL x3, at 96 */
    put_entry(buf + 46, 50938, 11, 2, 120, be);  /* FLOAT x2, at 120 */
    put_entry(buf + 58, 50939, 11, 64, 200, be); /* past the end */
    memcpy(buf + 80, "Test Camera", 12);
    put32(buf + 96, (uint32_t)8309, be);
    put32(buf + 100, 10000, be);
    put32(buf + 104, (uint32_t)-1786, be);
    put32(buf + 108, 10000, be);
    put32(buf + 112, 5, be);
    put32(buf + 116, 0, be);
    for (i = 0; i < 2; i++) {
        uint32_t bits = 0;

        memcpy(&bits, &floats[i], sizeof(bits));
        put32(buf + 120 + 4 * i, bits, be);
    }
    return 256;
}

static void test_order(int be) {
    unsigned char buf[256];
    size_t len = build(buf, be);
    N_TIFF* tiff = n_tiff_parse(buf, len);
    double v[4] = {0, 0, 0, 0};
    char text[64];

    check("the buffer parses", tiff != NULL);
    if (!tiff)
        return;
    check("the byte order is read", tiff->big_endian == be);
    check("the magic is reported", tiff->magic == N_TIFF_MAGIC_DCP);
    check("the entry past the end is skipped", tiff->count == 4 && !n_tiff_find(tiff, 50939));
    check("ASCII reads", n_tiff_get_ascii(tiff, 50708, text, sizeof(text)) == 0 &&
                             strcmp(text, "Test Camera") == 0);
    check("ASCII is cut to the room given",
          n_tiff_get_ascii(tiff, 50708, text, 5) == 0 && strcmp(text, "Test") == 0);
    check("a short in the entry reads", n_tiff_get_doubles(tiff, 50778, v, 4) == 1 && v[0] == 21.0);
    check("signed rationals read", n_tiff_get_doubles(tiff, 50721, v, 4) == 3 &&
                                       fabs(v[0] - 0.8309) < 1e-9 && fabs(v[1] + 0.1786) < 1e-9 &&
                                       v[2] == 0.0);
    check("floats read", n_tiff_get_doubles(tiff, 50938, v, 4) == 2 && v[0] == 1.5 && v[1] == -0.25);
    check("a count is reported", n_tiff_count(tiff, 50721) == 3 && n_tiff_count(tiff, 1) == 0);
    check("ASCII is not a number", n_tiff_get_doubles(tiff, 50708, v, 4) == 0);
    check("a number is not ASCII", n_tiff_get_ascii(tiff, 50721, text, sizeof(text)) == -1 &&
                                       text[0] == '\0');
    n_tiff_free(&tiff);
    check("free clears the pointer", tiff == NULL);
}

static void test_broken(void) {
    unsigned char buf[256];
    size_t len = build(buf, 0);

    check("a short buffer is refused", n_tiff_parse(buf, 6) == NULL);
    buf[0] = 'X';
    check("a bad byte order is refused", n_tiff_parse(buf, len) == NULL);
    build(buf, 0);
    put32(buf + 4, 250, 0);
    check("a directory past the end is refused", n_tiff_parse(buf, len) == NULL);
    build(buf, 0);
    put16(buf + 8, 60, 0);
    check("a directory longer than the buffer is refused", n_tiff_parse(buf, len) == NULL);
}

static int list_file(const char* path) {
    unsigned char* data = NULL;
    size_t len = 0;
    N_TIFF* tiff = NULL;
    size_t i = 0;

    if (n_file_read_bounded(path, 64 * 1024 * 1024, &data, &len) != TRUE || !data) {
        fprintf(stderr, "cannot read %s\n", path);
        return 1;
    }
    tiff = n_tiff_parse(data, len);
    if (!tiff) {
        fprintf(stderr, "%s is not TIFF structured\n", path);
        free(data);
        return 1;
    }
    printf("%s: %s endian, magic 0x%04x, %zu entries\n", path, tiff->big_endian ? "big" : "little",
           tiff->magic, tiff->count);
    for (i = 0; i < tiff->count; i++)
        printf("  tag %5u type %2u count %u\n", tiff->entries[i].tag, tiff->entries[i].type,
               tiff->entries[i].count);
    n_tiff_free(&tiff);
    free(data);
    return 0;
}

static void test_sub_ifd(int be) {
    unsigned char buf[64];
    N_TIFF* tiff = NULL;
    N_TIFF* exif = NULL;
    double offset = 0.0;
    char text[8];

    memset(buf, 0, sizeof(buf));
    buf[0] = buf[1] = (unsigned char)(be ? 'M' : 'I');
    put16(buf + 2, N_TIFF_MAGIC, be);
    put32(buf + 4, 8, be);
    /* IFD0: one entry, the EXIF pointer, a LONG holding 26. */
    put16(buf + 8, 1, be);
    put_entry(buf + 10, 0x8769, 4, 1, 26, be);
    /* At 26: one entry, SubSecTimeOriginal, "45" and its terminator, which
     * fit in the entry itself. */
    put16(buf + 26, 1, be);
    put_entry(buf + 28, 0x9291, 2, 3, 0, be);
    memcpy(buf + 28 + 8, "45", 3);

    tiff = n_tiff_parse(buf, sizeof(buf));
    check("sub-directory: IFD0 parses", tiff != NULL);
    check("sub-directory: the pointer reads", tiff && n_tiff_get_doubles(tiff, 0x8769, &offset, 1) == 1 &&
                                                  offset == 26.0);
    check("sub-directory: its tag is not in IFD0", tiff && n_tiff_find(tiff, 0x9291) == NULL);
    exif = n_tiff_parse_ifd(buf, sizeof(buf), (size_t)offset);
    check("sub-directory: it parses at the offset", exif != NULL);
    check("sub-directory: its ASCII reads", exif && n_tiff_get_ascii(exif, 0x9291, text, sizeof(text)) == 0 &&
                                                strcmp(text, "45") == 0);
    check("sub-directory: it takes the header's byte order", exif && exif->big_endian == be);
    n_tiff_free(&exif);
    n_tiff_free(&tiff);
    check("sub-directory: an offset past the end is refused", n_tiff_parse_ifd(buf, sizeof(buf), 63) == NULL);
    check("sub-directory: an offset inside the header is refused", n_tiff_parse_ifd(buf, sizeof(buf), 4) == NULL);
    /* A count that runs past the end. */
    put16(buf + 26, 9, be);
    check("sub-directory: a directory longer than the buffer is refused",
          n_tiff_parse_ifd(buf, sizeof(buf), 26) == NULL);
}

int main(int argc, char** argv) {
    int opt = 0, log_level = LOG_ERR;

    while ((opt = getopt(argc, argv, "V:")) != -1) {
        if (opt != 'V') {
            fprintf(stderr, "Usage: %s [-V LOG_LEVEL] [file]\n", argv[0]);
            return 2;
        }
        log_level = atoi(optarg);
    }
    set_log_level(log_level > 0 ? log_level : LOG_ERR);
    if (optind < argc)
        return list_file(argv[optind]);
    test_order(0);
    test_order(1);
    test_sub_ifd(0);
    test_sub_ifd(1);
    test_broken();
    printf("%s: %d failure(s)\n", failures ? "FAILED" : "ok", failures);
    return failures ? 1 : 0;
}