/*
 * Copyright (c) 2012 Red Hat Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *     * Redistributions of source code must retain the above
 *       copyright notice, this list of conditions and the
 *       following disclaimer.
 *     * Redistributions in binary form must reproduce the
 *       above copyright notice, this list of conditions and
 *       the following disclaimer in the documentation and/or
 *       other materials provided with the distribution.
 *     * The names of contributors to this software may not be
 *       used to endorse or promote products derived from this
 *       software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 *
 * Author: Stef Walter <stefw@gnome.org>
 */

#include "config.h"
#include "test.h"

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

#include "compat.h"
#include "pem.h"

struct {
	const char *input;
	struct {
		const char *type;
		const char *data;
		unsigned int length;
	} output[8];
} success_fixtures[] = {
	{
	  /* one block */
	  "-----BEGIN BLOCK1-----\n"
	  "aYNNXqshlVxCdo8QfKeXh3GUzd/yn4LYIVgQrx4a\n"
	  "-----END BLOCK1-----",
	  {
	    {
	      "BLOCK1",
	      "\x69\x83\x4d\x5e\xab\x21\x95\x5c\x42\x76\x8f\x10\x7c\xa7\x97\x87"
	      "\x71\x94\xcd\xdf\xf2\x9f\x82\xd8\x21\x58\x10\xaf\x1e\x1a",
	      30,
	    },
	    {
	      NULL,
	    }
	  }
	},

	{
	  /* one block, with header */
	  "-----BEGIN BLOCK1-----\n"
	  "Header1: value1 \n"
	  " Header2: value2\n"
	  "\n"
	  "aYNNXqshlVxCdo8QfKeXh3GUzd/yn4LYIVgQrx4a\n"
	  "-----END BLOCK1-----",
	  {
	    {
	      "BLOCK1",
	      "\x69\x83\x4d\x5e\xab\x21\x95\x5c\x42\x76\x8f\x10\x7c\xa7\x97\x87"
	      "\x71\x94\xcd\xdf\xf2\x9f\x82\xd8\x21\x58\x10\xaf\x1e\x1a",
	      30,
	    },
	    {
	      NULL,
	    }
	  }
	},

	{
	  /* two blocks, junk data */
	  "-----BEGIN BLOCK1-----\n"
	  "aYNNXqshlVxCdo8QfKeXh3GUzd/yn4LYIVgQrx4a\n"
	  "-----END BLOCK1-----\n"
	  "blah blah\n"
	  "-----BEGIN TWO-----\n"
	  "oy5L157C671HyJMCf9FiK9prvPZfSch6V4EoUfylFoI1Bq6SbL53kg==\n"
	  "-----END TWO-----\n"
	  "trailing data",
	  {
	    {
	      "BLOCK1",
	      "\x69\x83\x4d\x5e\xab\x21\x95\x5c\x42\x76\x8f\x10\x7c\xa7\x97\x87"
	      "\x71\x94\xcd\xdf\xf2\x9f\x82\xd8\x21\x58\x10\xaf\x1e\x1a",
	      30,
	    },
	    {
	      "TWO",
	      "\xa3\x2e\x4b\xd7\x9e\xc2\xeb\xbd\x47\xc8\x93\x02\x7f\xd1\x62\x2b"
	      "\xda\x6b\xbc\xf6\x5f\x49\xc8\x7a\x57\x81\x28\x51\xfc\xa5\x16\x82"
	      "\x35\x06\xae\x92\x6c\xbe\x77\x92",
	      40
	    },
	    {
	      NULL,
	    }
	  }
	},

	{
	  /* one block with empty type */
	  "-----BEGIN -----\n"
	  "aYNNXqshlVxCdo8QfKeXh3GUzd/yn4LYIVgQrx4a\n"
	  "-----END -----",
	  {
	    {
	      "",
	      "\x69\x83\x4d\x5e\xab\x21\x95\x5c\x42\x76\x8f\x10\x7c\xa7\x97\x87"
	      "\x71\x94\xcd\xdf\xf2\x9f\x82\xd8\x21\x58\x10\xaf\x1e\x1a",
	      30,
	    },
	    {
	      NULL,
	    }
	  }
	},

	{
	  NULL,
	}
};

typedef struct {
	int input_index;
	int output_index;
	int parsed;
} Closure;

static void
on_parse_pem_success (const char *type,
                      const unsigned char *contents,
                      size_t length,
                      void *user_data)
{
	Closure *cl = user_data;

	assert_num_eq (success_fixtures[cl->input_index].output[cl->output_index].length, length);
	assert (memcmp (success_fixtures[cl->input_index].output[cl->output_index].data, contents,
	                              success_fixtures[cl->input_index].output[cl->output_index].length) == 0);

	cl->output_index++;
	cl->parsed++;
}

static void
test_pem_success (void)
{
	Closure cl;
	int ret;
	int i;
	int j;

	for (i = 0; success_fixtures[i].input != NULL; i++) {
		cl.input_index = i;
		cl.output_index = 0;
		cl.parsed = 0;

		ret = p11_pem_parse (success_fixtures[i].input, strlen (success_fixtures[i].input),
		                     on_parse_pem_success, &cl);

		assert (success_fixtures[i].output[cl.output_index].type == NULL);

		/* Count number of outputs, return from p11_pem_parse() should match */
		for (j = 0; success_fixtures[i].output[j].type != NULL; j++);
		assert_num_eq (j, ret);
		assert_num_eq (ret, cl.parsed);
	}
}

const char *failure_fixtures[] = {
	/* too short at end of opening line */
	"-----BEGIN BLOCK1---\n"
	"aYNNXqshlVxCdo8QfKeXh3GUzd/yn4LYIVgQrx4a\n"
	"-----END BLOCK1-----",

	/* truncated */
	"-----BEGIN BLOCK1---",

	/* no ending */
	"-----BEGIN BLOCK1-----\n"
	"aYNNXqshlVxCdo8QfKeXh3GUzd/yn4LYIVgQrx4a\n",

	/* wrong ending */
	"-----BEGIN BLOCK1-----\n"
	"aYNNXqshlVxCdo8QfKeXh3GUzd/yn4LYIVgQrx4a\n"
	"-----END BLOCK2-----",

	/* wrong ending */
	"-----BEGIN BLOCK1-----\n"
	"aYNNXqshlVxCdo8QfKeXh3GUzd/yn4LYIVgQrx4a\n"
	"-----END INVALID-----",

	/* too short at end of ending line */
	"-----BEGIN BLOCK1-----\n"
	"aYNNXqshlVxCdo8QfKeXh3GUzd/yn4LYIVgQrx4a\n"
	"-----END BLOCK1---",

	/* invalid base64 data */
	"-----BEGIN BLOCK1-----\n"
	"!!!!NNXqshlVxCdo8QfKeXh3GUzd/yn4LYIVgQrx4a\n"
	"-----END BLOCK1-----",

	NULL,
};

static void
on_parse_pem_failure (const char *type,
                      const unsigned char *contents,
                      size_t length,
                      void *user_data)
{
	assert (false && "not reached");
}

static void
test_pem_failure (void)
{
	int ret;
	int i;

	for (i = 0; failure_fixtures[i] != NULL; i++) {
		ret = p11_pem_parse (failure_fixtures[i], strlen (failure_fixtures[i]),
		                     on_parse_pem_failure, NULL);
		assert_num_eq (0, ret);
	}
}

typedef struct {
	const char *input;
	size_t length;
	const char *type;
	const char *output;
} WriteFixture;

static WriteFixture write_fixtures[] = {
	{
	  "\x69\x83\x4d\x5e\xab\x21\x95\x5c\x42\x76\x8f\x10\x7c\xa7\x97\x87"
	  "\x71\x94\xcd\xdf\xf2\x9f\x82\xd8\x21\x58\x10\xaf\x1e\x1a",
	  30, "BLOCK1",
	  "-----BEGIN BLOCK1-----\n"
	  "aYNNXqshlVxCdo8QfKeXh3GUzd/yn4LYIVgQrx4a\n"
	  "-----END BLOCK1-----\n",
	},
	{
	  "\x50\x31\x31\x2d\x4b\x49\x54\x0a\x0a\x50\x72\x6f\x76\x69\x64\x65"
	  "\x73\x20\x61\x20\x77\x61\x79\x20\x74\x6f\x20\x6c\x6f\x61\x64\x20"
	  "\x61\x6e\x64\x20\x65\x6e\x75\x6d\x65\x72\x61\x74\x65\x20\x50\x4b"
	  "\x43\x53\x23\x31\x31\x20\x6d\x6f\x64\x75\x6c\x65\x73\x2e\x20\x50"
	  "\x72\x6f\x76\x69\x64\x65\x73\x20\x61\x20\x73\x74\x61\x6e\x64\x61"
	  "\x72\x64\x0a\x63\x6f\x6e\x66\x69\x67\x75\x72\x61\x74\x69\x6f\x6e"
	  "\x20\x73\x65\x74\x75\x70\x20\x66\x6f\x72\x20\x69\x6e\x73\x74\x61"
	  "\x6c\x6c\x69\x6e\x67\x20\x50\x4b\x43\x53\x23\x31\x31\x20\x6d\x6f"
	  "\x64\x75\x6c\x65\x73\x20\x69\x6e\x20\x73\x75\x63\x68\x20\x61\x20"
	  "\x77\x61\x79\x20\x74\x68\x61\x74\x20\x74\x68\x65\x79\x27\x72\x65"
	  "\x0a\x64\x69\x73\x63\x6f\x76\x65\x72\x61\x62\x6c\x65\x2e\x0a\x0a"
	  "\x41\x6c\x73\x6f\x20\x73\x6f\x6c\x76\x65\x73\x20\x70\x72\x6f\x62"
	  "\x6c\x65\x6d\x73\x20\x77\x69\x74\x68\x20\x63\x6f\x6f\x72\x64\x69"
	  "\x6e\x61\x74\x69\x6e\x67\x20\x74\x68\x65\x20\x75\x73\x65\x20\x6f"
	  "\x66\x20\x50\x4b\x43\x53\x23\x31\x31\x20\x62\x79\x20\x64\x69\x66"
	  "\x66\x65\x72\x65\x6e\x74\x0a\x63\x6f\x6d\x70\x6f\x6e\x65\x6e\x74"
	  "\x73\x20\x6f\x72\x20\x6c\x69\x62\x72\x61\x72\x69\x65\x73\x20\x6c"
	  "\x69\x76\x69\x6e\x67\x20\x69\x6e\x20\x74\x68\x65\x20\x73\x61\x6d"
	  "\x65\x20\x70\x72\x6f\x63\x65\x73\x73\x2e\x0a",
	  299, "LONG TYPE WITH SPACES",
	  "-----BEGIN LONG TYPE WITH SPACES-----\n"
	  "UDExLUtJVAoKUHJvdmlkZXMgYSB3YXkgdG8gbG9hZCBhbmQgZW51bWVyYXRlIFBL\n"
	  "Q1MjMTEgbW9kdWxlcy4gUHJvdmlkZXMgYSBzdGFuZGFyZApjb25maWd1cmF0aW9u\n"
	  "IHNldHVwIGZvciBpbnN0YWxsaW5nIFBLQ1MjMTEgbW9kdWxlcyBpbiBzdWNoIGEg\n"
	  "d2F5IHRoYXQgdGhleSdyZQpkaXNjb3ZlcmFibGUuCgpBbHNvIHNvbHZlcyBwcm9i\n"
	  "bGVtcyB3aXRoIGNvb3JkaW5hdGluZyB0aGUgdXNlIG9mIFBLQ1MjMTEgYnkgZGlm\n"
	  "ZmVyZW50CmNvbXBvbmVudHMgb3IgbGlicmFyaWVzIGxpdmluZyBpbiB0aGUgc2Ft\n"
	  "ZSBwcm9jZXNzLgo=\n"
	  "-----END LONG TYPE WITH SPACES-----\n"
	},
	{
	  "\x69\x83\x4d\x5e\xab\x21\x95\x5c\x42\x76\x8f\x10\x7c\xa7\x97\x87"
	  "\x71\x94\xcd\xdf\xf2\x9f\x82\xd8\x21\x58\x10\xaf",
	  28, "BLOCK1",
	  "-----BEGIN BLOCK1-----\n"
	  "aYNNXqshlVxCdo8QfKeXh3GUzd/yn4LYIVgQrw==\n"
	  "-----END BLOCK1-----\n",
	},
	{
	  NULL,
	}
};

static void
on_parse_written (const char *type,
                  const unsigned char *contents,
                  size_t length,
                  void *user_data)
{
	WriteFixture *fixture = user_data;

	assert_str_eq (fixture->type, type);
	assert_num_eq (fixture->length, length);
	assert (memcmp (contents, fixture->input, length) == 0);
}

static void
test_pem_write (void)
{
	WriteFixture *fixture;
	p11_buffer buf;
	unsigned int count;
	int i;

	for (i = 0; write_fixtures[i].input != NULL; i++) {
		fixture = write_fixtures + i;

		if (!p11_buffer_init_null (&buf, 0))
			assert_not_reached ();

		if (!p11_pem_write ((unsigned char *)fixture->input,
		                    fixture->length,
		                    fixture->type, &buf))
			assert_not_reached ();
		assert_str_eq (fixture->output, buf.data);
		assert_num_eq (strlen (fixture->output), buf.len);

		count = p11_pem_parse (buf.data, buf.len, on_parse_written, fixture);
		assert_num_eq (1, count);

		p11_buffer_uninit (&buf);
	}
}

int
main (int argc,
      char *argv[])
{
	p11_test (test_pem_success, "/pem/success");
	p11_test (test_pem_failure, "/pem/failure");
	p11_test (test_pem_write, "/pem/write");
	return p11_test_run (argc, argv);
}
