#include <sys/types.h>

#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static int
parse_offset(const char *text, off_t *value)
{
	char *end;
	long long parsed;

	errno = 0;
	parsed = strtoll(text, &end, 10);
	if (errno != 0 || end == text || *end != '\0' || parsed < 0)
		return (-1);
	*value = (off_t)parsed;
	return (0);
}

static int
parse_size(const char *text, size_t *value)
{
	char *end;
	unsigned long parsed;

	errno = 0;
	parsed = strtoul(text, &end, 10);
	if (errno != 0 || end == text || *end != '\0' || parsed == 0 ||
	    parsed > 1024 * 1024)
		return (-1);
	*value = (size_t)parsed;
	return (0);
}

static int
check_range(const char *path, const char *reference, off_t offset, size_t size)
{
	unsigned char *actual, *expected;
	ssize_t actual_count, expected_count;
	int actual_fd, expected_fd, result;

	actual = malloc(size);
	expected = malloc(size);
	if (actual == NULL || expected == NULL)
		return (1);
	actual_fd = open(path, O_RDONLY);
	expected_fd = open(reference, O_RDONLY);
	if (actual_fd < 0 || expected_fd < 0) {
		perror("open B27 range input");
		return (1);
	}
	actual_count = pread(actual_fd, actual, size, offset);
	expected_count = pread(expected_fd, expected, size, offset);
	result = actual_count == (ssize_t)size && expected_count == (ssize_t)size &&
	    memcmp(actual, expected, size) == 0 ? 0 : 1;
	if (result != 0)
		fprintf(stderr, "range mismatch offset=%jd size=%zu actual=%zd expected=%zd errno=%d\n",
		    (intmax_t)offset, size, actual_count, expected_count, errno);
	close(actual_fd);
	close(expected_fd);
	free(actual);
	free(expected);
	return (result);
}

static int
check_errno(const char *path, int expected_errno)
{
	unsigned char buffer[4096];
	ssize_t count;
	int descriptor;

	descriptor = open(path, O_RDONLY);
	if (descriptor < 0) {
		if (errno == expected_errno)
			return (0);
		perror("open B27 damaged payload");
		return (1);
	}
	for (;;) {
		errno = 0;
		count = read(descriptor, buffer, sizeof(buffer));
		if (count < 0) {
			int actual_errno = errno;

			close(descriptor);
			if (actual_errno == expected_errno)
				return (0);
			fprintf(stderr, "read errno=%d expected=%d\n", actual_errno,
			    expected_errno);
			return (1);
		}
		if (count == 0) {
			close(descriptor);
			fprintf(stderr, "damaged stream reached clean EOF\n");
			return (1);
		}
	}
}

int
main(int argc, char **argv)
{
	off_t offset;
	size_t size;

	if (argc == 6 && strcmp(argv[1], "range") == 0 &&
	    parse_offset(argv[4], &offset) == 0 &&
	    parse_size(argv[5], &size) == 0)
		return (check_range(argv[2], argv[3], offset, size));
	if (argc == 4 && strcmp(argv[1], "errno") == 0)
		return (check_errno(argv[2], atoi(argv[3])));
	fprintf(stderr, "usage: %s range PATH REFERENCE OFFSET SIZE | errno PATH ERRNO\n",
	    argv[0]);
	return (2);
}
