diff --git a/Dockerfile b/Dockerfile
index ef39da2c78dd36360300f8f8edf3d36e020def43..e3683390b4ef98919c51516334d40cc606366341 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,8 +4,6 @@ RUN echo "Hello from Docker"
 RUN mkdir -p /linux/tools/labs/skels/assignments/1-tracer
 RUN mkdir -p /linux/tools/labs/skels/assignments/1-tracer-checker
 
-COPY ./checker/1-tracer-checker /linux/tools/labs/skels/assignments/1-tracer-checker
-
 COPY ./checker/checker_daemons/so2_vm_checker_daemon.sh /linux/tools/labs/rootfs/etc/init.d
 RUN chmod +x /linux/tools/labs/rootfs/etc/init.d/so2_vm_checker_daemon.sh
 RUN chroot /linux/tools/labs/rootfs update-rc.d so2_vm_checker_daemon.sh defaults
diff --git a/checker/2-uart-checker/Makefile b/checker/2-uart-checker/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..9cb87cb293e9cca5710751fab0cded1f2908aaae
--- /dev/null
+++ b/checker/2-uart-checker/Makefile
@@ -0,0 +1,14 @@
+CFLAGS = -Wall -g -static -m32
+
+.PHONY: all run clean
+
+all: test solution.ko
+
+test: _test/test.o
+	$(CC) $(CFLAGS) -o $@ $^
+
+solution.ko: _test/solution.ko
+	ln -s $< $@
+
+clean:
+	-rm -f *~ test _test/test.o solution.ko
diff --git a/checker/2-uart-checker/README b/checker/2-uart-checker/README
new file mode 100644
index 0000000000000000000000000000000000000000..67ae7234cd192c44e00caf9fe5aa4e26bcbf9296
--- /dev/null
+++ b/checker/2-uart-checker/README
@@ -0,0 +1,38 @@
+= UART16550 TEST SUITE ==
+
+Test suite for UART16550
+
+== FILES ==
+
+README
+	* this file
+
+Makefile
+	* Makefile to build the test suite executable
+
+_checker
+	* script to run all tests defined in _test/test.c
+
+_test/test.c
+	* test suite for UART16550
+
+_test/solution.ko
+	* kernel module implementing UART16550,
+	used to transmit/receive data to/from your kernel module
+
+== BUILDING ==
+
+Use the Makefile to properly build the test executable:
+
+	make
+
+== RUNNING ==
+
+Copy your uart16550.ko module and _checker, test and solution.ko
+to fsimg/root directory on your QEMU/KVM virtual machine.
+
+In order to run the test suite you can use the _checker script.
+
+The _checker script runs all tests and computes assignment grade:
+
+	./_checker
diff --git a/checker/2-uart-checker/_checker b/checker/2-uart-checker/_checker
new file mode 100755
index 0000000000000000000000000000000000000000..a118d02b621c642d8d2bd960594f21bf4d1cda5f
--- /dev/null
+++ b/checker/2-uart-checker/_checker
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+insmod uart16550.ko; cat /proc/modules > /dev/kmsg; rmmod uart16550
+./test
diff --git a/checker/2-uart-checker/_test/solution.ko b/checker/2-uart-checker/_test/solution.ko
new file mode 100644
index 0000000000000000000000000000000000000000..996dd21ac572b8fd91bc14645e9bf6e57f4093d8
Binary files /dev/null and b/checker/2-uart-checker/_test/solution.ko differ
diff --git a/checker/2-uart-checker/_test/test.c b/checker/2-uart-checker/_test/test.c
new file mode 100644
index 0000000000000000000000000000000000000000..6e87f7055daac8f8750c8a7c301bcf8c4f4d148e
--- /dev/null
+++ b/checker/2-uart-checker/_test/test.c
@@ -0,0 +1,593 @@
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <time.h>
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <signal.h>
+
+#include "uart16550.h"
+
+#define UART16550_MAJOR		42
+#define COM1_MAJOR		42
+#define COM2_MAJOR		42
+#define STR(x)			#x
+#define XSTR(x)			STR(x)
+#define OPTION_COM1_ONLY	1
+#define OPTION_COM2_ONLY	2
+#define OPTION_BOTH		3
+
+#define MODULE_NAME		"uart16550"
+#define SOLUTION_NAME		"solution"
+
+#define PAD_CHARS		60
+
+#define UART0			"/dev/uart0"
+#define UART1			"/dev/uart1"
+#define UART10			"/dev/uart10"
+
+#define INFILE			"testfile.in"
+#define OUTFILE			"testfile.out"
+
+#define fail(s)		do {				\
+		printf("%s:%d: ", __func__, __LINE__);	\
+		fflush(stdout);				\
+		perror(s);				\
+		exit(EXIT_FAILURE);			\
+	} while (0)
+
+
+#define test(d, v, e, p)		do_test((d), (v), (e), 0, 0, (p))
+#define not_test(d, v, e, p)		do_test((d), (v), (e), 1, 0, (p))
+#define fatal_test(d, v, e,p)		do_test((d), (v), (e), 0, 1, (p))
+
+#define GENERIC_TEST_TIMEOUT 3
+const int total = 92;
+
+void sig_handler(int signum) {
+	fprintf(stderr, "Child process pid=%d of checker (that issues read/write syscalls to the driver) got killed after TIMEOUT=%ds\n", getpid(), GENERIC_TEST_TIMEOUT);
+	fprintf(stderr, "\tThis might be because you didn't implement read/write or there is a bug in the implementation\n");
+	exit(EXIT_FAILURE);
+}
+
+/*
+ * if the test passes it will return 0
+ * if it fails it returns the number of points given as argument
+ */
+static float
+do_test(const char *description, int value, int expected, int negate, int fatal, float points)
+{
+	int num_chars;
+
+	num_chars = printf("%s", description);
+	for (; num_chars < PAD_CHARS - strlen("passed"); num_chars++)
+		putchar('.');
+	fflush(stdout);
+	if (!negate) {
+		if (value == expected) {
+			printf("passed [%.1f/%d]\n", points, total);
+			fflush(stdout);
+			return 0;
+		} else {
+			printf("failed [0/%d]\n", total);
+			fflush(stdout);
+			if (fatal)
+				exit(EXIT_FAILURE);
+		}
+	} else {
+		if (value != expected) {
+			printf("passed [%.1f/%d]\n", points, total);
+			fflush(stdout);
+			return 0;
+		} else {
+			printf("failed [0/%d]\n", total);
+			fflush(stdout);
+			if (fatal)
+				exit(EXIT_FAILURE);
+		}
+	}
+	return points;
+}
+
+static void
+test_title(const char *title)
+{
+	int len = strlen(title);
+	int pad = (PAD_CHARS - len) / 2 - 1;
+	int mod = (PAD_CHARS - len) % 2;
+	int i;
+
+	assert(pad >= 1);
+	putchar('\n');
+	for (i = 0; i < pad; i++)
+		putchar('=');
+	printf(" %s ", title);
+	for (i = 0; i < pad + mod; i++)
+		putchar('=');
+	putchar('\n');
+}
+
+static void
+make_nodes(void)
+{
+	mknod(UART0, S_IFCHR, COM1_MAJOR<<8);
+	mknod(UART1, S_IFCHR, (COM2_MAJOR<<8) + 1);
+	mknod(UART10, S_IFCHR, (UART16550_MAJOR<<8)+10);
+}
+
+static void
+remove_nodes(void)
+{
+	unlink(UART0);
+	unlink(UART1);
+	unlink(UART10);
+}
+
+static float
+test1(void)
+{
+	float total = 16;
+
+	test_title("Test 1. Module insertion and removal");
+
+	/* Insert module with default params and test. */
+	total -= fatal_test("insmod " MODULE_NAME ", default options",
+			system("insmod " MODULE_NAME ".ko"), 0, 1);
+	total -= test("major",
+			system("cat /proc/devices | grep '" XSTR(COM1_MAJOR) " " MODULE_NAME "' >/dev/null 2>&1"),
+			0, 1);
+	total -= test("ioports COM1",
+			system("cat /proc/ioports | grep '03f8-03ff : " MODULE_NAME "' > /dev/null 2>&1"),
+			0, 1);
+	total -= test("ioports COM2",
+			system("cat /proc/ioports | grep '02f8-02ff : " MODULE_NAME "' > /dev/null 2>&1"),
+			0, 1);
+	total -= test("interrupts COM1",
+			system("cat /proc/interrupts | grep '4:.*" MODULE_NAME "' > /dev/null 2>&1"),
+			0, 1);
+	total -= test("interrupts COM2",
+			system("cat /proc/interrupts | grep '3:.*" MODULE_NAME "' > /dev/null 2>&1"),
+			0, 1);
+	total -= test("rmmod", system("rmmod " MODULE_NAME), 0, 0.5);
+
+	/* Insert module with different major. */
+	total -= fatal_test("insmod " MODULE_NAME ", major=" XSTR(COM2_MAJOR),
+			system("insmod " MODULE_NAME ".ko major=" XSTR(COM2_MAJOR)), 0, 1);
+	total -= test("major",
+			system("cat /proc/devices | grep '" XSTR(COM2_MAJOR) " " MODULE_NAME "' >/dev/null 2>&1"),
+			0, 1);
+	total -= test("rmmod", system("rmmod " MODULE_NAME), 0, 0.5);
+
+	/* Insert module only for COM2, check that it works side by side
+	 * with solution.
+	 */
+	total -= fatal_test("insmod " MODULE_NAME ", COM2 only",
+			system("insmod " MODULE_NAME ".ko option=" XSTR(OPTION_COM2_ONLY)),
+			0, 1);
+	total -= fatal_test("insmod " SOLUTION_NAME ", COM1 only",
+			system("insmod " SOLUTION_NAME ".ko option=" XSTR(OPTION_COM1_ONLY)),
+			0, 1);
+	total -= test("ioports COM1",
+			system("cat /proc/ioports | grep '03f8-03ff : " SOLUTION_NAME "' > /dev/null 2>&1"),
+			0, 1);
+	total -= test("ioports COM2",
+			system("cat /proc/ioports | grep '02f8-02ff : " MODULE_NAME "' > /dev/null 2>&1"),
+			0, 1);
+	total -= test("interrupts COM1",
+			system("cat /proc/interrupts | grep '4:.*" SOLUTION_NAME "' > /dev/null 2>&1"),
+			0, 1);
+	total -= test("interrupts COM2",
+			system("cat /proc/interrupts | grep '3:.*" MODULE_NAME "' > /dev/null 2>&1"),
+			0, 1);
+	total -= test("rmmod " MODULE_NAME, system("rmmod " MODULE_NAME), 0, 0.5);
+	total -= test("rmmod " SOLUTION_NAME, system("rmmod " SOLUTION_NAME), 0, 0.5);
+
+	return total;
+}
+
+static float
+test2(void)
+{
+	float total = 5.5;
+	int fd;
+
+	test_title("Test 2. Invalid parameters");
+
+	/* Check ioctl sanity. */
+	total -= fatal_test("insmod", system("insmod " MODULE_NAME ".ko"), 0, 1);
+	fd = open(UART0, O_RDWR);
+	if (fd == -1)
+		fail("open " UART0);
+#define ioctl_test(n)	test("invalid ioctl " XSTR((n)), \
+		ioctl(fd, UART16550_IOCTL_SET_LINE, (n)), -1, 1)
+	total -= ioctl_test(0xdeadbeef);
+	total -= ioctl_test(0x1337cafe);
+#undef ioctl_test
+	total -= test("invalid ioctl wrong operation", ioctl(fd, 0xffff), -1, 1);
+	close(fd);
+	total -= test("rmmod", system("rmmod " MODULE_NAME), 0, 0.5);
+
+	/* Check invalid module parameters. */
+	total -= not_test("insmod " MODULE_NAME ", option=0xdeadbabe",
+			system("insmod " MODULE_NAME ".ko option=0xdeadbabe"),
+			0, 1);
+
+	return total;
+}
+
+/* Speed sets:
+ *	0 -> 1200, 2400, 4800
+ *	1 -> 9600, 19200, 38400, 56000
+ *	2 -> 115200
+ */
+static const struct {
+	int num;
+	unsigned char speed[4];
+	int bufsizes[2];		/* min and max */
+} speed_sets[3] = {
+	{
+		.num = 3,
+		.speed = { UART16550_BAUD_1200,
+			UART16550_BAUD_2400,
+			UART16550_BAUD_4800, -1 },
+		.bufsizes = { 128, 256 },
+	},
+	{
+		.num = 4,
+		.speed = { UART16550_BAUD_9600,
+			UART16550_BAUD_19200,
+			UART16550_BAUD_38400,
+			UART16550_BAUD_56000 },
+		.bufsizes = { 256, 1024 },
+	},
+	{
+		.num = 1,
+		.speed = { UART16550_BAUD_115200, -1, -1, -1 },
+		.bufsizes = { 2048, 2048 },
+	},
+};
+
+static void
+gen_params(struct uart16550_line_info *line, int speed_set)
+{
+	int r;
+
+	line->baud = speed_sets[speed_set].speed[rand() %
+		speed_sets[speed_set].num];
+	line->len = UART16550_LEN_8;
+	line->stop = rand() % 2 * 4;
+	r = rand() % 4;
+	line->par = r < 2 ? r*8 : 0x18 + (r-2) * 8;
+}
+
+int do_read(int fd, unsigned char *buffer, int size)
+{
+	int n, from = 0;
+
+	while (1) {
+		n = read(fd, &buffer[from], size - from);
+		if (n <= 0)
+			return -1;
+		if (n + from == size)
+			return 0;
+		from += n;
+	}
+}
+
+int do_write(int fd, unsigned char *buffer, unsigned int size)
+{
+	int n, from = 0;
+
+	while (1) {
+		n = write(fd, &buffer[from], size - from);
+		if (n <= 0) {
+			perror("write");
+			return -1;
+		}
+		if (n + from == size)
+			return 0;
+		from += n;
+	}
+}
+
+static int
+gen_test_file(char *fname, int speed_set)
+{
+	int size, min, max;
+	char comm[1024];
+
+	min = speed_sets[speed_set].bufsizes[0];
+	max = speed_sets[speed_set].bufsizes[1];
+	size = (min == max) ? min : rand() % (min - max) + min;
+	sprintf(comm,
+			"dd if=/dev/urandom of=%s bs=1 count=%d >/dev/null 2>/dev/null",
+			fname,
+			size);
+	if (system(comm))
+		fprintf(stderr, "failed to generate random file (%s)\n", comm);
+	return size;
+}
+
+static void
+copy_file(int fdr, int fdw, int len)
+{
+#define COPY_BUF_SIZE		128
+	unsigned char buf[COPY_BUF_SIZE];
+
+	do {
+		int partial, rc;
+
+		partial = len < COPY_BUF_SIZE ? len : COPY_BUF_SIZE;
+		if (partial == 0)
+			break;
+		rc = read(fdr, buf, partial);
+		if (rc == 0)
+			break;
+		if (rc == -1)
+			fail("read");
+		len -= rc;
+		rc = do_write(fdw, buf, rc);
+		if (rc < 0)
+			fail("write");
+	} while (1);
+}
+
+static int
+copy_test(int fd0, int fd1, int speed_set)
+{
+	pid_t rpid, wpid, kpid;
+	int len, status, fd;
+	int rc1, rc2, rc3, exit_status1, exit_status2, exit_status3;
+	int i;
+
+	len = gen_test_file(INFILE, speed_set);
+	rpid = fork();
+	switch (rpid) {
+	case 0:
+		fd = open(INFILE, O_RDONLY);
+		if (fd < 0)
+			fail("open " INFILE);
+		copy_file(fd, fd0, len);
+		close(fd);
+		exit(EXIT_SUCCESS);
+		break;
+	default:
+		break;
+	}
+
+	wpid = fork();
+	switch (wpid) {
+	case 0:
+		fd = open(OUTFILE, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+		if (fd < 0)
+			fail("open " OUTFILE);
+		copy_file(fd1, fd, len);
+		close(fd);
+		exit(EXIT_SUCCESS);
+		break;
+	default:
+		break;
+	}
+
+	kpid = fork();
+	switch (kpid) {
+	case 0:
+		for (i = 0; i < GENERIC_TEST_TIMEOUT; i++) {
+			/*
+			 * check if procs still exist. kill with arg 0
+			 * will succed (ret 0) if the pid exists
+			 */
+			if (!kill(rpid, 0)) {
+				sleep(1);
+				continue;
+			} else if (!kill(wpid, 0)) {
+				sleep(1);
+				continue;
+			} else
+				break;
+
+		}
+		kill(rpid, SIGTERM);
+		kill(wpid, SIGTERM);
+		exit(EXIT_SUCCESS);
+		break;
+	default:
+		break;
+
+	}
+
+	rc1 = waitpid(rpid, &status, 0);
+	exit_status1 = WEXITSTATUS(status);
+		
+
+	rc2 = waitpid(wpid, &status, 0);
+	exit_status2 = WEXITSTATUS(status);
+
+	rc3 = waitpid(kpid, &status, 0);
+	exit_status3 = WEXITSTATUS(status);
+
+	if (rc1 < 0 || rc2 < 0 || rc3 < 0 || 
+		exit_status1 || exit_status2 || exit_status3)
+		return -1;
+
+	return system("diff " INFILE " " OUTFILE "> /dev/null 2> /dev/null");
+}
+
+static float
+generic_test(const char *reader, const char *writer, int speed_set,
+		int num_tests)
+{
+	int fd0, fd1, i;
+	float total = num_tests * 1.5 + (reader != writer ? 6 : 4) * 0.5;
+	char dbuf[1024], cbuf[1024];
+	struct uart16550_line_info uli;
+
+	if (reader != writer) {
+		sprintf(dbuf, "insmod %s", reader);
+		sprintf(cbuf, "insmod %s.ko option=%d",
+				reader, OPTION_COM2_ONLY);
+		total -= fatal_test(dbuf, system(cbuf), 0, 0.5);
+		sprintf(dbuf, "insmod %s", writer);
+		sprintf(cbuf, "insmod %s.ko option=%d",
+				writer, OPTION_COM1_ONLY);
+		total -= fatal_test(dbuf, system(cbuf), 0, 0.5);
+	} else {
+		sprintf(dbuf, "insmod %s", reader);
+		sprintf(cbuf, "insmod %s.ko", reader);
+		total -= fatal_test(dbuf, system(cbuf), 0, 0.5);
+	}
+
+	gen_params(&uli, speed_set);
+	fd0 = open(UART0, O_WRONLY);
+	if (fd0 == -1)
+		fail("open " UART0);
+	fd1 = open(UART1, O_RDONLY);
+	if (fd1 == -1)
+		fail("open " UART1);
+	total -= test("ioctl reader",
+			ioctl(fd1, UART16550_IOCTL_SET_LINE, &uli), 0, 0.5);
+	total -= test("ioctl writer",
+			ioctl(fd0, UART16550_IOCTL_SET_LINE, &uli), 0, 0.5);
+
+	for (i = 0; i < num_tests; i++) {
+		sprintf(dbuf, "test %02d", i + 1);
+		total -= test(dbuf, copy_test(fd0, fd1, speed_set), 0, 1.5);
+	}
+
+	close(fd0);
+	close(fd1);
+
+	if (reader != writer) {
+		sprintf(dbuf, "rmmod %s", reader);
+		sprintf(cbuf, "rmmod %s.ko", reader);
+		total -= fatal_test(dbuf, system(cbuf), 0, 0.5);
+		sprintf(dbuf, "rmmod %s", writer);
+		sprintf(cbuf, "rmmod %s.ko", writer);
+		total -= fatal_test(dbuf, system(cbuf), 0, 0.5);
+	} else {
+		sprintf(dbuf, "rmmod %s", reader);
+		sprintf(cbuf, "rmmod %s.ko", reader);
+		total -= fatal_test(dbuf, system(cbuf), 0, 0.5);
+	}
+
+	return total;
+}
+
+#define choose_one(rd, wr)		do {			\
+		int r = rand() % 2;				\
+		if (r == 0) {					\
+			rd = MODULE_NAME;			\
+			wr = SOLUTION_NAME;			\
+		} else {					\
+			rd = SOLUTION_NAME;			\
+			wr = MODULE_NAME;			\
+		}						\
+	} while (0)
+
+static float
+test3(void)
+{
+	const char *rd, *wr;
+
+	rd = MODULE_NAME;
+	wr = SOLUTION_NAME;
+	test_title("Test 3. Read, small speed");
+	return generic_test(rd, wr, 0, 5);
+}
+
+static float
+test4(void)
+{
+	const char *rd, *wr;
+
+	rd = SOLUTION_NAME;
+	wr = MODULE_NAME;
+	test_title("Test 4. Write, small speed");
+	return generic_test(rd, wr, 0, 5);
+}
+
+static float
+test5(void)
+{
+	const char *rd, *wr;
+
+	rd = wr = MODULE_NAME;
+	test_title("Test 5. Back-to-back, small speed");
+	return generic_test(rd, wr, 0, 5);
+}
+
+static float
+test6(void)
+{
+	const char *rd, *wr;
+
+	choose_one(rd, wr);
+	test_title("Test 6. Read/Write, medium speed");
+	return generic_test(rd, wr, 1, 5);
+}
+
+static float
+test7(void)
+{
+	const char *rd, *wr;
+
+	rd = wr = MODULE_NAME;
+	test_title("Test 7. Back-to-back, medium speed");
+	return generic_test(rd, wr, 1, 5);
+}
+
+static float
+test8(void)
+{
+	const char *rd, *wr;
+
+	choose_one(rd, wr);
+	test_title("Test 8. Read/Write, high speed");
+	return generic_test(rd, wr, 2, 5);
+}
+
+static float
+test9(void)
+{
+	const char *rd, *wr;
+
+	rd = wr = MODULE_NAME;
+	test_title("Test 9. Back-to-back, high speed");
+	return generic_test(rd, wr, 2, 5);
+}
+
+int
+main(void)
+{
+	float num_passed = 0;
+
+	signal(SIGTERM, sig_handler);
+	srand(time(NULL));
+	make_nodes();
+
+	num_passed += test1();
+	num_passed += test2();
+	num_passed += test3();
+	num_passed += test4();
+	num_passed += test5();
+	num_passed += test6();
+	num_passed += test7();
+	num_passed += test8();
+	num_passed += test9();
+
+	remove_nodes();
+	unlink(INFILE);
+	unlink(OUTFILE);
+	printf("\nTotal: [%.1f/%d]\n", num_passed, total);
+
+	return 0;
+}
+
+/* Extra 2 lines so the file is the proper size. */
diff --git a/checker/2-uart-checker/_test/uart16550.h b/checker/2-uart-checker/_test/uart16550.h
new file mode 100644
index 0000000000000000000000000000000000000000..73008921925769a4081b10d563f84a8bf5ead49b
--- /dev/null
+++ b/checker/2-uart-checker/_test/uart16550.h
@@ -0,0 +1,46 @@
+#ifndef UART16550_H
+#define UART16550_H
+
+#define OPTION_COM1			1
+#define OPTION_COM2			2
+#define OPTION_BOTH			3
+
+#define UART16550_COM1_SELECTED		0x01
+#define UART16550_COM2_SELECTED		0x02
+
+#define MAX_NUMBER_DEVICES		2
+
+#ifndef _UART16550_REGS_H
+
+#define UART16550_BAUD_1200		96
+#define UART16550_BAUD_2400		48
+#define UART16550_BAUD_4800		24
+#define UART16550_BAUD_9600		12
+#define UART16550_BAUD_19200		6
+#define UART16550_BAUD_38400		3
+#define UART16550_BAUD_56000		2
+#define UART16550_BAUD_115200		1
+
+#define UART16550_LEN_5			0x00
+#define UART16550_LEN_6			0x01
+#define UART16550_LEN_7			0x02
+#define UART16550_LEN_8			0x03
+
+#define UART16550_STOP_1		0x00
+#define UART16550_STOP_2		0x04
+
+#define UART16550_PAR_NONE		0x00
+#define UART16550_PAR_ODD		0x08
+#define UART16550_PAR_EVEN		0x18
+#define UART16550_PAR_STICK		0x20
+
+#endif
+
+#define UART16550_IOCTL_SET_LINE	1
+
+struct uart16550_line_info {
+	unsigned char baud, len, par, stop;
+};
+
+#endif
+
diff --git a/checker/3-raid-checker/Makefile b/checker/3-raid-checker/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..9eea19fbf00cbb82d1959e828adf44b886735199
--- /dev/null
+++ b/checker/3-raid-checker/Makefile
@@ -0,0 +1,14 @@
+CFLAGS = -Wall -Wextra -g -m32
+LDFLAGS = -static -m32
+
+.PHONY: all clean
+
+all:
+
+test:
+	make -C _test/
+	ln -sf _test/run-test run-test
+
+clean:
+	-make -C _test/ clean
+	rm -rf run-test
diff --git a/checker/3-raid-checker/README b/checker/3-raid-checker/README
new file mode 100644
index 0000000000000000000000000000000000000000..8fb41fd9b25275c7971aee7ea79c37006c0b7b00
--- /dev/null
+++ b/checker/3-raid-checker/README
@@ -0,0 +1,40 @@
+= SOFTWARE RAID TEST SUITE ==
+
+Test suite for software RAID
+
+== FILES ==
+
+README
+	* this file
+
+Makefile
+	* Makefile for automating the build process
+
+_checker
+	* script to run all tests defined in _test/test.c
+
+_test/test.c
+	* test suite for software RAID
+
+== RUNNING ==
+
+In order to run the test suite you can either use the _checker
+script or run the run-test executable.
+
+The kernel module must be named ssr.ko and must be in the current folder.
+
+The run-test executable has to be in the current folder. You can create
+a link using:
+
+	ln -sf _test/run-test run-test
+
+The _checker script runs all tests and computes assignment grade. You
+can use any of the two commands below.
+
+	make test
+	./_checker
+
+In order to run a specific test, pass the test number (1 .. 78) to the
+run-test executable.
+
+	./run-test 5
diff --git a/checker/3-raid-checker/_checker b/checker/3-raid-checker/_checker
new file mode 100755
index 0000000000000000000000000000000000000000..da4e4058d736be4eecbd54f4db21695ab5648100
--- /dev/null
+++ b/checker/3-raid-checker/_checker
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+/bin/dmesg -c > /dev/null 2>&1
+./run-test
diff --git a/checker/3-raid-checker/_test/Makefile b/checker/3-raid-checker/_test/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..a8a98a2cb2220d974e4d31ec76af85a70c2a5c8d
--- /dev/null
+++ b/checker/3-raid-checker/_test/Makefile
@@ -0,0 +1,15 @@
+CFLAGS = -Wall -Wextra -Wno-unused-function -g -m32
+LDFLAGS = -static -m32
+
+.PHONY: all clean
+
+all: run-test
+
+run-test: run-test.o test.o
+
+run-test.o: run-test.c run-test.h
+
+test.o: test.c run-test.h
+
+clean:
+	-rm -f *~ test.o run-test.o run-test test
diff --git a/checker/3-raid-checker/_test/run-test.c b/checker/3-raid-checker/_test/run-test.c
new file mode 100644
index 0000000000000000000000000000000000000000..d8f15caf33ee0eb18e842c6d910ba87dfddf7fa0
--- /dev/null
+++ b/checker/3-raid-checker/_test/run-test.c
@@ -0,0 +1,106 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+
+#include "run-test.h"
+
+/* Enable/disable exiting when program fails. */
+//#define EXIT_IF_FAIL
+
+static size_t test_index;
+static size_t total_points = 0;
+
+static void test_do_fail(size_t points)
+{
+	printf("failed  [  0/%3zu]\n", max_points);
+	fflush(stdout);
+#ifdef EXIT_IF_FAIL
+	exit(EXIT_FAILURE);
+#endif
+}
+
+static void test_do_pass(size_t points)
+{
+	total_points += points;
+	printf("passed  [%3zu/%3zu]\n", points, max_points);
+	fflush(stdout);
+}
+
+void basic_test(int condition)
+{
+	size_t i;
+	char *description = test_array[test_index].description;
+	size_t desc_len = strlen(description);
+	size_t points = test_array[test_index].points;
+
+	printf("(%3zu) %s", test_index + 1, description);
+	for (i = 0; i < 56 - desc_len; i++)
+		printf(".");
+	if (condition)
+		test_do_pass(points);
+	else
+		test_do_fail(points);
+}
+
+static void print_test_total(void)
+{
+	size_t i;
+
+	for (i = 0; i < 62; i++)
+		printf(" ");
+	printf("Total:  [%3zu/%3zu]\n", total_points, max_points);
+}
+
+static void run_test(void)
+{
+	test_array[test_index].function();
+}
+
+int main(int argc, char **argv)
+{
+	size_t num_tests = get_num_tests();
+
+	if (argc > 2) {
+		fprintf(stderr, "Usage: %s [test_number]\n", argv[0]);
+		fprintf(stderr, "  1 <= test_number <= %zu\n", num_tests);
+		exit(EXIT_FAILURE);
+	}
+
+	/* Randomize time quantums. */
+	srand(time(NULL));
+
+	/* In case of no arguments run all tests. */
+	if (argc == 1) {
+		init_world();
+		for (test_index = 0; test_index < num_tests; test_index++)
+			run_test();
+		print_test_total();
+		cleanup_world();
+		return 0;
+	}
+
+	/* If provided, argument is test index. */
+	test_index = strtoul(argv[1], NULL, 10);
+	if (errno == EINVAL || errno == ERANGE) {
+		fprintf(stderr, "%s is not a number\n", argv[1]);
+		exit(EXIT_FAILURE);
+	}
+
+	if (test_index == 0 || test_index > num_tests) {
+		fprintf(stderr, "Error: Test index is out of range "
+				"(1 <= test_index <= %zu).\n", num_tests);
+		exit(EXIT_FAILURE);
+	}
+
+	/* test_index is one less than what the user provides. */
+	test_index--;
+
+	/* Run test_index test. */
+	init_world();
+	run_test();
+	cleanup_world();
+
+	return 0;
+}
diff --git a/checker/3-raid-checker/_test/run-test.h b/checker/3-raid-checker/_test/run-test.h
new file mode 100644
index 0000000000000000000000000000000000000000..e4d64f6aa1b375c097e045d59d740e755ce5b9d6
--- /dev/null
+++ b/checker/3-raid-checker/_test/run-test.h
@@ -0,0 +1,25 @@
+#ifndef _RUN_TEST_H_
+#define _RUN_TEST_H_
+
+/* functions exported by the framework */
+void basic_test(int condition);
+
+/* function exported by the test */
+void init_world(void);
+void cleanup_world(void);
+size_t get_num_tests(void);
+
+/* test function prototype */
+typedef void (test_f)(void);
+
+struct run_test_t {
+	test_f *function;		/* test/evaluation function */
+	char *description;		/* test description */
+	size_t points;			/* points for each test */
+};
+
+/* Use test_index to pass through test_array. */
+extern struct run_test_t test_array[];
+extern size_t max_points;
+
+#endif /* _RUN_TEST_H_ */
diff --git a/checker/3-raid-checker/_test/ssr.h b/checker/3-raid-checker/_test/ssr.h
new file mode 100644
index 0000000000000000000000000000000000000000..5aa4107fb15825ec455a6e68c6884a2e545a5861
--- /dev/null
+++ b/checker/3-raid-checker/_test/ssr.h
@@ -0,0 +1,26 @@
+/*
+ * Simple Software Raid - Linux header file
+ */
+
+#ifndef SSR_H_
+#define SSR_H_		1
+
+#define SSR_MAJOR		240
+#define SSR_FIRST_MINOR		0
+#define SSR_NUM_MINORS		1
+
+#define PHYSICAL_DISK1_NAME	"/dev/vdb"
+#define PHYSICAL_DISK2_NAME	"/dev/vdc"
+
+/* sector size */
+#define KERNEL_SECTOR_SIZE	512
+
+/* physical partition size - 95 MB (more than this results in error) */
+#define LOGICAL_DISK_NAME	"/dev/ssr"
+#define LOGICAL_DISK_SIZE	(95 * 1024 * 1024)
+#define LOGICAL_DISK_SECTORS	((LOGICAL_DISK_SIZE) / (KERNEL_SECTOR_SIZE))
+
+/* sync data */
+#define SSR_IOCTL_SYNC		1
+
+#endif
diff --git a/checker/3-raid-checker/_test/test.c b/checker/3-raid-checker/_test/test.c
new file mode 100644
index 0000000000000000000000000000000000000000..b6a36ec618aee137458c48289d667c82d909353a
--- /dev/null
+++ b/checker/3-raid-checker/_test/test.c
@@ -0,0 +1,1769 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include "run-test.h"
+#include "ssr.h"
+
+#define SSR_BASE_NAME		"ssr"
+#define SSR_LIN_EXT		".ko"
+#define SSR_MOD_NAME		SSR_BASE_NAME SSR_LIN_EXT
+
+#define CRC_SIZE		4
+
+#define ONE_SECTOR		KERNEL_SECTOR_SIZE
+#define ONE_PAGE		4096
+#define TWO_PAGES		8192
+#define TEN_PAGES		40960
+#define ONE_MEG			1048576
+
+/* Read/write buffers. */
+static unsigned char *log_rd_buf, *log_wr_buf;
+static unsigned char *phys1_rd_buf, *phys1_wr_buf;
+static unsigned char *phys2_rd_buf, *phys2_wr_buf;
+static unsigned char *log_rd_crc, *log_wr_crc;
+static unsigned char *phys1_rd_crc, *phys1_wr_crc;
+static unsigned char *phys2_rd_crc, *phys2_wr_crc;
+
+/* File descriptors. */
+static int log_fd, phys1_fd, phys2_fd;
+
+enum {
+	START = 0,
+	MIDDLE,
+	END
+};
+
+enum {
+	PHYS_FILL_DATA = 'P',
+	LOG_FILL_DATA = 'L',
+	CORRUPT_DATA = 'C',
+	PHYS1_DISK_DIRTY_DATA = 'a',
+	PHYS1_BUF_DIRTY_DATA = 'A',
+	PHYS2_DISK_DIRTY_DATA = 'b',
+	PHYS2_BUF_DIRTY_DATA = 'B',
+	LOG_DISK_DIRTY_DATA = 'd',
+	LOG_BUF_DIRTY_DATA = 'D',
+};
+
+/*
+ * "upgraded" read routine
+ */
+
+static ssize_t xread(int fd, void *buffer, size_t len)
+{
+	ssize_t ret;
+	ssize_t n;
+
+	n = 0;
+	while (n < (ssize_t) len) {
+		ret = read(fd, (char *) buffer + n, len - n);
+		if (ret < 0)
+			return -1;
+		if (ret == 0)
+			break;
+		n += ret;
+	}
+
+	return n;
+}
+
+/*
+ * "upgraded" write routine
+ */
+
+static ssize_t xwrite(int fd, const void *buffer, size_t len)
+{
+	ssize_t ret;
+	ssize_t n;
+
+	n = 0;
+	while (n < (ssize_t) len) {
+		ret = write(fd, (const char *) buffer + n, len - n);
+		if (ret < 0)
+			return -1;
+		if (ret == 0)
+			break;
+		n += ret;
+	}
+
+	return n;
+}
+
+/*
+ * Compute CRC32.
+ */
+
+static unsigned int crc32(unsigned int seed,
+		const unsigned char *p, unsigned int len)
+{
+	size_t i;
+	unsigned int crc = seed;
+
+	while (len--) {
+		crc ^= *p++;
+		for (i = 0; i < 8; i++)
+			crc = (crc >> 1) ^ ((crc & 1) ? 0xedb88320 : 0);
+	}
+
+	return crc;
+}
+
+static void compute_crc(const void *data_buffer, void *crc_buffer, size_t len)
+{
+	size_t i;
+	unsigned int crc;
+
+	for (i = 0; i < len; i += ONE_SECTOR) {
+		crc = crc32(0, (const unsigned char *) data_buffer + i, ONE_SECTOR);
+		memcpy((char *) crc_buffer + i / ONE_SECTOR * CRC_SIZE,
+				&crc, CRC_SIZE);
+	}
+}
+
+static off_t data_offset_from_whence(int whence, size_t len)
+{
+	switch (whence) {
+	case START:
+		return 0;
+	case MIDDLE:
+		return LOGICAL_DISK_SIZE / 2 - len;
+	case END:
+		return LOGICAL_DISK_SIZE - len;
+	default:
+		return -1;
+	}
+}
+
+static off_t crc_offset_from_whence(int whence, size_t len)
+{
+	off_t data_offset = data_offset_from_whence(whence, len);
+
+	return LOGICAL_DISK_SIZE + data_offset / ONE_SECTOR * CRC_SIZE;
+}
+
+static void fill_buffer(void *buffer, int c, size_t len)
+{
+	memset(buffer, c, len);
+}
+
+static void log_fill_buffer(size_t len)
+{
+	fill_buffer(log_wr_buf, LOG_FILL_DATA, len);
+}
+
+static void phys_fill_buffer(size_t len)
+{
+	fill_buffer(phys1_wr_buf, PHYS_FILL_DATA, len);
+	fill_buffer(phys2_wr_buf, PHYS_FILL_DATA, len);
+}
+
+static ssize_t read_whence_data(int fd, void *buffer, size_t len, int whence)
+{
+	off_t offset = data_offset_from_whence(whence, len);
+
+	lseek(fd, offset, SEEK_SET);
+	return xread(fd, buffer, len);
+}
+
+static ssize_t read_whence_crc(int fd, void *crc_buffer, size_t data_len,
+		int whence)
+{
+	off_t offset = crc_offset_from_whence(whence, data_len);
+
+	lseek(fd, offset, SEEK_SET);
+	return xread(fd, crc_buffer, data_len / ONE_SECTOR * CRC_SIZE);
+}
+
+static ssize_t write_whence_data(int fd, const void *buffer,
+		size_t len, int whence)
+{
+	off_t offset = data_offset_from_whence(whence, len);
+
+	lseek(fd, offset, SEEK_SET);
+	return xwrite(fd, buffer, len);
+}
+
+static ssize_t write_whence_crc(int fd, void *crc_buffer, size_t data_len,
+		int whence)
+{
+	off_t offset = crc_offset_from_whence(whence, data_len);
+
+	lseek(fd, offset, SEEK_SET);
+	return xwrite(fd, crc_buffer, data_len / ONE_SECTOR * CRC_SIZE);
+}
+
+static ssize_t log_read_whence(size_t len, int whence)
+{
+	ssize_t n;
+
+	n = read_whence_data(log_fd, log_rd_buf, len, whence);
+	if (n < 0)
+		return -1;
+	compute_crc(log_rd_buf, log_rd_crc, len);
+	return n;
+}
+
+static ssize_t log_write_whence(size_t len, int whence)
+{
+	compute_crc(log_wr_buf, log_wr_crc, len);
+	return write_whence_data(log_fd, log_wr_buf, len, whence);
+}
+
+static ssize_t phys_read_whence(size_t id, size_t len, int whence)
+{
+	ssize_t n_data, n_crc;
+	int fd = ((id == 1) ? phys1_fd : phys2_fd);
+
+	unsigned char *data_buf = ((id == 1) ? phys1_rd_buf : phys2_rd_buf);
+	unsigned char *crc_buf = ((id == 1) ? phys1_rd_crc : phys2_rd_crc);
+
+	n_data = read_whence_data(fd, data_buf, len, whence);
+	if (n_data < 0)
+		return -1;
+	n_crc = read_whence_crc(fd, crc_buf, len, whence);
+	if (n_crc < 0)
+		return -1;
+	return n_data;
+}
+
+static ssize_t phys_write_whence(size_t id, size_t len, int whence)
+{
+	ssize_t n_data, n_crc;
+	int fd = ((id == 1) ? phys1_fd : phys2_fd);
+	unsigned char *data_buf = ((id == 1) ? phys1_wr_buf : phys2_wr_buf);
+	unsigned char *crc_buf = ((id == 1) ? phys1_wr_crc : phys2_wr_crc);
+
+	compute_crc(data_buf, crc_buf, len);
+	n_data = write_whence_data(fd, data_buf, len, whence);
+	if (n_data < 0)
+		return -1;
+	n_crc = write_whence_crc(fd, crc_buf, len, whence);
+	if (n_crc < 0)
+		return -1;
+	return n_data;
+}
+
+static void corrupt_buffer(void *buffer, size_t sectors)
+{
+	size_t i;
+
+	for (i = 0; i < sectors; i++)
+		((unsigned char *) buffer)[i * ONE_SECTOR] = CORRUPT_DATA;
+}
+
+static ssize_t phys_corrupt_and_write_whence(size_t id, size_t len,
+		size_t sectors, int whence)
+{
+	ssize_t n_data, n_crc;
+	int fd = ((id == 1) ? phys1_fd : phys2_fd);
+	unsigned char *data_buf = ((id == 1) ? phys1_wr_buf : phys2_wr_buf);
+	unsigned char *crc_buf = ((id == 1) ? phys1_wr_crc : phys2_wr_crc);
+
+	compute_crc(data_buf, crc_buf, len);
+	corrupt_buffer(data_buf, sectors);
+	n_data = write_whence_data(fd, data_buf, len, whence);
+	if (n_data < 0)
+		return -1;
+	n_crc = write_whence_crc(fd, crc_buf, len, whence);
+	if (n_crc < 0)
+		return -1;
+	return n_data;
+}
+
+static ssize_t log_read_start(size_t len)
+{
+	return log_read_whence(len, START);
+}
+
+static ssize_t log_read_middle(size_t len)
+{
+	return log_read_whence(len, MIDDLE);
+}
+
+static ssize_t log_read_end(size_t len)
+{
+	return log_read_whence(len, END);
+}
+
+static ssize_t log_write_start(size_t len)
+{
+	return log_write_whence(len, START);
+}
+
+static ssize_t log_write_middle(size_t len)
+{
+	return log_write_whence(len, MIDDLE);
+}
+
+static ssize_t log_write_end(size_t len)
+{
+	return log_write_whence(len, END);
+}
+
+static ssize_t phys1_read_start(size_t len)
+{
+	return phys_read_whence(1, len, START);
+}
+
+#if 0
+static ssize_t phys1_read_middle(size_t len)
+{
+	return phys_read_whence(1, len, MIDDLE);
+}
+
+static ssize_t phys1_read_end(size_t len)
+{
+	return phys_read_whence(1, len, END);
+}
+#endif
+
+static ssize_t phys1_write_start(size_t len)
+{
+	return phys_write_whence(1, len, START);
+}
+
+static ssize_t phys1_corrupt_and_write_start(size_t len, size_t sectors)
+{
+	return phys_corrupt_and_write_whence(1, len, sectors, START);
+}
+
+#if 0
+static ssize_t phys1_write_middle(size_t len)
+{
+	return phys_write_whence(1, len, MIDDLE);
+}
+
+static ssize_t phys1_write_end(size_t len)
+{
+	return phys_write_whence(1, len, END);
+}
+#endif
+
+static ssize_t phys2_read_start(size_t len)
+{
+	return phys_read_whence(2, len, START);
+}
+
+#if 0
+static ssize_t phys2_read_middle(size_t len)
+{
+	return phys_read_whence(2, len, MIDDLE);
+}
+
+static ssize_t phys2_read_end(size_t len)
+{
+	return phys_read_whence(2, len, END);
+}
+#endif
+
+static ssize_t phys2_write_start(size_t len)
+{
+	return phys_write_whence(2, len, START);
+}
+
+static ssize_t phys2_corrupt_and_write_start(size_t len, size_t sectors)
+{
+	return phys_corrupt_and_write_whence(2, len, sectors, START);
+}
+
+#if 0
+static ssize_t phys2_write_middle(size_t len)
+{
+	return phys_write_whence(2, len, MIDDLE);
+}
+
+static ssize_t phys2_write_end(size_t len)
+{
+	return phys_write_whence(2, len, END);
+}
+#endif
+
+static int cmp_data_log_rd_phys1_wr(size_t len)
+{
+	return memcmp(log_rd_buf, phys1_wr_buf, len);
+}
+
+static int cmp_data_log_rd_phys2_wr(size_t len)
+{
+	return memcmp(log_rd_buf, phys2_wr_buf, len);
+}
+
+static int cmp_data_log_rd_phys1_rd(size_t len)
+{
+	return memcmp(log_rd_buf, phys1_rd_buf, len);
+}
+
+static int cmp_data_log_rd_phys2_rd(size_t len)
+{
+	return memcmp(log_rd_buf, phys2_rd_buf, len);
+}
+
+static int cmp_data_log_wr_phys1_rd(size_t len)
+{
+	return memcmp(log_wr_buf, phys1_rd_buf, len);
+}
+
+static int cmp_data_log_wr_phys2_rd(size_t len)
+{
+	return memcmp(log_wr_buf, phys2_rd_buf, len);
+}
+
+static int cmp_crc_log_rd_phys1_wr(size_t data_len)
+{
+	return memcmp(log_rd_crc, phys1_wr_crc, data_len / ONE_SECTOR * CRC_SIZE);
+}
+
+static int cmp_crc_log_rd_phys2_wr(size_t data_len)
+{
+	return memcmp(log_rd_crc, phys2_wr_crc, data_len / ONE_SECTOR * CRC_SIZE);
+}
+
+static int cmp_crc_log_rd_phys1_rd(size_t data_len)
+{
+	return memcmp(log_rd_crc, phys1_rd_crc, data_len / ONE_SECTOR * CRC_SIZE);
+}
+
+static int cmp_crc_log_rd_phys2_rd(size_t data_len)
+{
+	return memcmp(log_rd_crc, phys2_rd_crc, data_len / ONE_SECTOR * CRC_SIZE);
+}
+
+static int cmp_crc_log_wr_phys1_rd(size_t data_len)
+{
+	return memcmp(log_wr_crc, phys1_rd_crc, data_len / ONE_SECTOR * CRC_SIZE);
+}
+
+static int cmp_crc_log_wr_phys2_rd(size_t data_len)
+{
+	return memcmp(log_wr_crc, phys2_rd_crc, data_len / ONE_SECTOR * CRC_SIZE);
+}
+
+static void drop_caches(void)
+{
+	int fd;
+	char buf[] = "1\n";
+
+	fd = open("/proc/sys/vm/drop_caches", O_WRONLY);
+	assert(fd >= 0);
+	write(fd, buf, strlen(buf));
+	close(fd);
+}
+
+static void flush_disk_buffers(void)
+{
+	sync();
+	//system("/bin/echo 1 > /proc/sys/vm/drop_caches");
+	drop_caches();
+}
+
+static void dump_data(const void *buf, size_t len, const char *header)
+{
+	size_t i;
+
+	printf("%s:", header);
+	for (i = 0; i < len / sizeof(unsigned int); i++) {
+		if (i % 4 == 0)
+			printf("\n\t");
+		printf(" %08x", ((unsigned int *) buf)[i]);
+	}
+	printf("\n\n");
+}
+
+void init_world(void)
+{
+	/* Cleanup if required. */
+	flush_disk_buffers();
+	system("/sbin/rmmod " SSR_BASE_NAME " > /dev/null 2>&1");
+	system("/bin/cat /proc/devices | /bin/grep " SSR_BASE_NAME " > /dev/null");
+	system("/bin/rm -f " LOGICAL_DISK_NAME);
+
+	assert(system("/sbin/insmod " SSR_MOD_NAME) == 0);
+	assert(system("/bin/cat /proc/devices | /bin/grep " SSR_BASE_NAME
+				" > /dev/null") == 0);
+	assert(access(PHYSICAL_DISK1_NAME, F_OK) == 0);
+	assert(access(PHYSICAL_DISK2_NAME, F_OK) == 0);
+	assert(access(LOGICAL_DISK_NAME, F_OK) == 0);
+
+	log_rd_buf = calloc(1024 * 1024, 1);
+	assert(log_rd_buf != NULL);
+	log_wr_buf = calloc(1024 * 1024, 1);
+	assert(log_rd_buf != NULL);
+	phys1_rd_buf = calloc(1024 * 1024, 1);
+	assert(phys1_rd_buf != NULL);
+	phys1_wr_buf = calloc(1024 * 1024, 1);
+	assert(phys1_wr_buf != NULL);
+	phys2_rd_buf = calloc(1024 * 1024, 1);
+	assert(phys2_rd_buf != NULL);
+	phys2_wr_buf = calloc(1024 * 1024, 1);
+	assert(phys2_wr_buf != NULL);
+	log_rd_crc = calloc(8 * 1024, 1);
+	assert(log_rd_crc != NULL);
+	log_wr_crc = calloc(8 * 1024, 1);
+	assert(log_rd_crc != NULL);
+	phys1_rd_crc = calloc(8 * 1024, 1);
+	assert(phys1_rd_crc != NULL);
+	phys1_wr_crc = calloc(8 * 1024, 1);
+	assert(phys1_wr_crc != NULL);
+	phys2_rd_crc = calloc(8 * 1024, 1);
+	assert(phys2_rd_crc != NULL);
+	phys2_wr_crc = calloc(8 * 1024, 1);
+	assert(phys2_wr_crc != NULL);
+}
+
+void cleanup_world(void)
+{
+	flush_disk_buffers();
+	system("/sbin/rmmod " SSR_BASE_NAME);
+	system("/bin/cat /proc/devices | /bin/grep " SSR_BASE_NAME " > /dev/null");
+	system("/bin/rm -f " LOGICAL_DISK_NAME);
+	free(log_rd_buf); free(log_wr_buf);
+	free(phys1_rd_buf); free(phys1_wr_buf);
+	free(phys2_rd_buf); free(phys2_wr_buf);
+	free(log_rd_crc); free(log_wr_crc);
+	free(phys1_rd_crc); free(phys1_wr_crc);
+	free(phys2_rd_crc); free(phys2_wr_crc);
+}
+
+static void make_disks_dirty(void)
+{
+	fill_buffer(phys1_wr_buf, PHYS1_DISK_DIRTY_DATA, ONE_MEG);
+	fill_buffer(phys1_wr_crc, PHYS1_DISK_DIRTY_DATA, ONE_MEG / ONE_SECTOR * CRC_SIZE);
+	fill_buffer(phys2_wr_buf, PHYS2_DISK_DIRTY_DATA, ONE_MEG);
+	fill_buffer(phys2_wr_crc, PHYS2_DISK_DIRTY_DATA, ONE_MEG / ONE_SECTOR * CRC_SIZE);
+	phys1_write_start(ONE_MEG);
+	phys2_write_start(ONE_MEG);
+}
+
+static void make_buffers_dirty(void)
+{
+	fill_buffer(phys1_wr_buf, PHYS1_BUF_DIRTY_DATA, ONE_MEG);
+	fill_buffer(phys1_wr_crc, PHYS1_BUF_DIRTY_DATA, ONE_MEG / ONE_SECTOR * CRC_SIZE);
+	fill_buffer(phys1_rd_buf, PHYS1_BUF_DIRTY_DATA, ONE_MEG);
+	fill_buffer(phys1_rd_crc, PHYS1_BUF_DIRTY_DATA, ONE_MEG / ONE_SECTOR * CRC_SIZE);
+	fill_buffer(phys2_wr_buf, PHYS2_BUF_DIRTY_DATA, ONE_MEG);
+	fill_buffer(phys2_wr_crc, PHYS2_BUF_DIRTY_DATA, ONE_MEG / ONE_SECTOR * CRC_SIZE);
+	fill_buffer(phys2_rd_buf, PHYS2_BUF_DIRTY_DATA, ONE_MEG);
+	fill_buffer(phys2_rd_crc, PHYS2_BUF_DIRTY_DATA, ONE_MEG / ONE_SECTOR * CRC_SIZE);
+	fill_buffer(log_wr_buf, LOG_BUF_DIRTY_DATA, ONE_MEG);
+	fill_buffer(log_wr_crc, LOG_BUF_DIRTY_DATA, ONE_MEG / ONE_SECTOR * CRC_SIZE);
+	fill_buffer(log_rd_buf, LOG_BUF_DIRTY_DATA, ONE_MEG);
+	fill_buffer(log_rd_crc, LOG_BUF_DIRTY_DATA, ONE_MEG / ONE_SECTOR * CRC_SIZE);
+}
+
+static void init_test(void)
+{
+	flush_disk_buffers();
+	log_fd = open(LOGICAL_DISK_NAME, O_RDWR);
+	assert(log_fd >= 0);
+	phys1_fd = open(PHYSICAL_DISK1_NAME, O_RDWR);
+	assert(phys1_fd >= 0);
+	phys2_fd = open(PHYSICAL_DISK2_NAME, O_RDWR);
+	assert(phys2_fd >= 0);
+	make_disks_dirty();
+	make_buffers_dirty();
+	flush_disk_buffers();
+}
+
+static void cleanup_test(void)
+{
+	close(log_fd);
+	close(phys1_fd);
+	close(phys2_fd);
+}
+
+static void open_logical(void)
+{
+	int fd;
+
+	fd = open(LOGICAL_DISK_NAME, O_RDWR);
+	basic_test(fd >= 0);
+	close(fd);
+}
+
+static void close_logical(void)
+{
+	int fd, rc;
+
+	fd = open(LOGICAL_DISK_NAME, O_RDWR);
+	rc = close(fd);
+	basic_test(rc == 0);
+}
+
+static void use_after_close_invalid(void)
+{
+	int fd, val;
+	ssize_t n;
+
+	fd = open(LOGICAL_DISK_NAME, O_RDWR);
+	close(fd);
+	n = read(fd, &val, sizeof(val));
+	basic_test(n < 0);
+}
+
+static void lseek_logical(void)
+{
+	off_t offset;
+
+	init_test();
+	offset = lseek(log_fd, LOGICAL_DISK_SIZE / 2, SEEK_SET);
+	basic_test(offset == LOGICAL_DISK_SIZE / 2);
+	cleanup_test();
+}
+
+static void read_one_sector_start(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_read_start(ONE_SECTOR);
+	basic_test(n == ONE_SECTOR);
+	cleanup_test();
+}
+
+static void read_one_sector_middle(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_read_middle(ONE_SECTOR);
+	basic_test(n == ONE_SECTOR);
+	cleanup_test();
+}
+
+static void read_one_sector_end(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_read_end(ONE_SECTOR);
+	basic_test(n == ONE_SECTOR);
+	cleanup_test();
+}
+
+static void write_one_sector_start(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_write_start(ONE_SECTOR);
+	basic_test(n == ONE_SECTOR);
+	cleanup_test();
+}
+
+static void write_one_sector_middle(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_write_middle(ONE_SECTOR);
+	basic_test(n == ONE_SECTOR);
+	cleanup_test();
+}
+
+static void write_one_sector_end(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_write_end(ONE_SECTOR);
+	basic_test(n == ONE_SECTOR);
+	cleanup_test();
+}
+
+static void read_one_page_start(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_read_start(ONE_PAGE);
+	basic_test(n == ONE_PAGE);
+	cleanup_test();
+}
+
+static void read_one_page_middle(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_read_middle(ONE_PAGE);
+	basic_test(n == ONE_PAGE);
+	cleanup_test();
+}
+
+static void read_one_page_end(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_read_end(ONE_PAGE);
+	basic_test(n == ONE_PAGE);
+	cleanup_test();
+}
+
+static void write_one_page_start(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_write_start(ONE_PAGE);
+	basic_test(n == ONE_PAGE);
+	cleanup_test();
+}
+
+static void write_one_page_middle(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_write_middle(ONE_PAGE);
+	basic_test(n == ONE_PAGE);
+	cleanup_test();
+}
+
+static void write_one_page_end(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_write_end(ONE_PAGE);
+	basic_test(n == ONE_PAGE);
+	cleanup_test();
+}
+
+static void read_two_pages_start(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_read_start(TWO_PAGES);
+	basic_test(n == TWO_PAGES);
+	cleanup_test();
+}
+
+static void read_two_pages_middle(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_read_middle(TWO_PAGES);
+	basic_test(n == TWO_PAGES);
+	cleanup_test();
+}
+
+static void read_two_pages_end(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_read_end(TWO_PAGES);
+	basic_test(n == TWO_PAGES);
+	cleanup_test();
+}
+
+static void write_two_pages_start(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_write_start(TWO_PAGES);
+	basic_test(n == TWO_PAGES);
+	cleanup_test();
+}
+
+static void write_two_pages_middle(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_write_middle(TWO_PAGES);
+	basic_test(n == TWO_PAGES);
+	cleanup_test();
+}
+
+static void write_two_pages_end(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_write_end(TWO_PAGES);
+	basic_test(n == TWO_PAGES);
+	cleanup_test();
+}
+
+static void read_one_meg_start(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_read_start(ONE_MEG);
+	basic_test(n == ONE_MEG);
+	cleanup_test();
+}
+
+static void read_one_meg_middle(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_read_middle(ONE_MEG);
+	basic_test(n == ONE_MEG);
+	cleanup_test();
+}
+
+static void read_one_meg_end(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_read_end(ONE_MEG);
+	basic_test(n == ONE_MEG);
+	cleanup_test();
+}
+
+static void write_one_meg_start(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_write_start(ONE_MEG);
+	basic_test(n == ONE_MEG);
+	cleanup_test();
+}
+
+static void write_one_meg_middle(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_write_middle(ONE_MEG);
+	basic_test(n == ONE_MEG);
+	cleanup_test();
+}
+
+static void write_one_meg_end(void)
+{
+	ssize_t n;
+
+	init_test();
+	n = log_write_end(ONE_MEG);
+	basic_test(n == ONE_MEG);
+	cleanup_test();
+}
+
+static void read_boundary_one_sector(void)
+{
+	ssize_t n;
+
+	init_test();
+	lseek(log_fd, LOGICAL_DISK_SIZE, SEEK_SET);
+	n = xread(log_fd, log_rd_buf, ONE_SECTOR);
+	basic_test(n == 0);
+	cleanup_test();
+}
+
+static void read_boundary_one_page(void)
+{
+	ssize_t n;
+
+	init_test();
+	lseek(log_fd, LOGICAL_DISK_SIZE, SEEK_SET);
+	n = xread(log_fd, log_rd_buf, ONE_PAGE);
+	basic_test(n == 0);
+	cleanup_test();
+}
+
+static void read_boundary_two_pages(void)
+{
+	ssize_t n;
+
+	init_test();
+	lseek(log_fd, LOGICAL_DISK_SIZE, SEEK_SET);
+	n = xread(log_fd, log_rd_buf, TWO_PAGES);
+	basic_test(n == 0);
+	cleanup_test();
+}
+
+static void read_boundary_one_meg(void)
+{
+	ssize_t n;
+
+	init_test();
+	lseek(log_fd, LOGICAL_DISK_SIZE, SEEK_SET);
+	n = xread(log_fd, log_rd_buf, ONE_MEG);
+	basic_test(n == 0);
+	cleanup_test();
+}
+
+static void write_boundary_one_sector(void)
+{
+	ssize_t n;
+
+	init_test();
+	lseek(log_fd, LOGICAL_DISK_SIZE, SEEK_SET);
+	n = xwrite(log_fd, log_rd_buf, ONE_SECTOR);
+	basic_test(n < 0);
+	cleanup_test();
+}
+
+static void write_boundary_one_page(void)
+{
+	ssize_t n;
+
+	init_test();
+	lseek(log_fd, LOGICAL_DISK_SIZE, SEEK_SET);
+	n = xwrite(log_fd, log_wr_buf, ONE_PAGE);
+	basic_test(n < 0);
+	cleanup_test();
+}
+
+static void write_boundary_two_pages(void)
+{
+	ssize_t n;
+
+	init_test();
+	lseek(log_fd, LOGICAL_DISK_SIZE, SEEK_SET);
+	n = xwrite(log_fd, log_wr_buf, TWO_PAGES);
+	basic_test(n < 0);
+	cleanup_test();
+}
+
+static void write_boundary_one_meg(void)
+{
+	ssize_t n;
+
+	init_test();
+	lseek(log_fd, LOGICAL_DISK_SIZE, SEEK_SET);
+	n = xwrite(log_fd, log_wr_buf, ONE_MEG);
+	basic_test(n < 0);
+	cleanup_test();
+}
+
+static size_t get_free_memory(void)
+{
+	FILE *f;
+	size_t i;
+	char buf[256];
+	char *p;
+
+	f = fopen("/proc/meminfo", "rt");
+	assert(f != NULL);
+	/* Second line is 'MemFree: ...' */
+	fgets(buf, 256, f);
+	fgets(buf, 256, f);
+	fclose(f);
+
+	p = NULL;
+	for (i = 0; i < 256; i++)
+		if (buf[i] == ':') {
+			p = buf+i+1;
+			break;
+		}
+
+	return strtoul(p, NULL, 10);
+}
+
+static void memory_is_freed(void)
+{
+	size_t mem_used_before, mem_used_after;
+	size_t i;
+
+	init_test();
+	mem_used_before = get_free_memory();
+	for (i = 0; i < 5; i++)
+		log_write_start(ONE_MEG);
+	mem_used_after = get_free_memory();
+
+	/* We assume 3MB (3072KB) is a reasonable memory usage in writes. */
+	basic_test(mem_used_after < mem_used_before + 3072 &&
+			mem_used_before < mem_used_after + 3072);
+	cleanup_test();
+}
+
+static void write_one_sector_check_phys1(void)
+{
+	int rc;
+	size_t len = ONE_SECTOR;
+
+	init_test();
+	log_fill_buffer(len);
+	log_write_start(len);
+	flush_disk_buffers();
+	phys1_read_start(len);
+	rc = cmp_data_log_wr_phys1_rd(len);
+	basic_test(rc == 0);
+	cleanup_test();
+}
+
+static void write_one_page_check_phys1(void)
+{
+	int rc;
+	size_t len = ONE_PAGE;
+
+	init_test();
+	log_fill_buffer(len);
+	log_write_start(len);
+	flush_disk_buffers();
+	phys1_read_start(len);
+	rc = cmp_data_log_wr_phys1_rd(len);
+	basic_test(rc == 0);
+	cleanup_test();
+}
+
+static void write_two_pages_check_phys1(void)
+{
+	int rc;
+	size_t len = TWO_PAGES;
+
+	init_test();
+	log_fill_buffer(len);
+	log_write_start(len);
+	flush_disk_buffers();
+	phys1_read_start(len);
+	rc = cmp_data_log_wr_phys1_rd(len);
+	basic_test(rc == 0);
+	cleanup_test();
+}
+
+static void write_one_meg_check_phys1(void)
+{
+	int rc;
+	size_t len = ONE_MEG;
+
+	init_test();
+	log_fill_buffer(len);
+	log_write_start(len);
+	flush_disk_buffers();
+	phys1_read_start(len);
+	rc = cmp_data_log_wr_phys1_rd(len);
+	basic_test(rc == 0);
+	cleanup_test();
+}
+
+static void write_one_sector_check_phys(void)
+{
+	int rc1, rc2;
+	size_t len = ONE_SECTOR;
+
+	init_test();
+	log_fill_buffer(len);
+	log_write_start(len);
+	flush_disk_buffers();
+	phys1_read_start(len);
+	phys2_read_start(len);
+	rc1 = cmp_data_log_wr_phys1_rd(len);
+	rc2 = cmp_data_log_wr_phys2_rd(len);
+	basic_test(rc1 == 0 && rc2 == 0);
+	cleanup_test();
+}
+
+static void write_one_page_check_phys(void)
+{
+	int rc1, rc2;
+	size_t len = ONE_PAGE;
+
+	init_test();
+	log_fill_buffer(len);
+	log_write_start(len);
+	flush_disk_buffers();
+	phys1_read_start(len);
+	phys2_read_start(len);
+	rc1 = cmp_data_log_wr_phys1_rd(len);
+	rc2 = cmp_data_log_wr_phys2_rd(len);
+	basic_test(rc1 == 0 && rc2 == 0);
+	cleanup_test();
+}
+
+static void write_two_pages_check_phys(void)
+{
+	int rc1, rc2;
+	size_t len = TWO_PAGES;
+
+	init_test();
+	log_fill_buffer(len);
+	log_write_start(len);
+	flush_disk_buffers();
+	phys1_read_start(len);
+	phys2_read_start(len);
+	rc1 = cmp_data_log_wr_phys1_rd(len);
+	rc2 = cmp_data_log_wr_phys2_rd(len);
+	basic_test(rc1 == 0 && rc2 == 0);
+	cleanup_test();
+}
+
+static void write_one_meg_check_phys(void)
+{
+	int rc1, rc2;
+	size_t len = ONE_MEG;
+
+	init_test();
+	log_fill_buffer(len);
+	log_write_start(len);
+	flush_disk_buffers();
+	phys1_read_start(len);
+	phys2_read_start(len);
+	rc1 = cmp_data_log_wr_phys1_rd(len);
+	rc2 = cmp_data_log_wr_phys2_rd(len);
+	basic_test(rc1 == 0 && rc2 == 0);
+	cleanup_test();
+}
+
+static void read_one_sector_after_write(void)
+{
+	int rc;
+	size_t len = ONE_SECTOR;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_write_start(len);
+	phys2_write_start(len);
+	flush_disk_buffers();
+	log_read_start(len);
+	rc = cmp_data_log_rd_phys1_wr(len);
+	basic_test(rc == 0);
+	cleanup_test();
+}
+
+static void read_one_page_after_write(void)
+{
+	int rc;
+	size_t len = ONE_PAGE;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_write_start(len);
+	phys2_write_start(len);
+	flush_disk_buffers();
+	log_read_start(len);
+	rc = cmp_data_log_rd_phys1_wr(len);
+	basic_test(rc == 0);
+	cleanup_test();
+}
+
+static void read_two_pages_after_write(void)
+{
+	int rc;
+	size_t len = TWO_PAGES;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_write_start(len);
+	phys2_write_start(len);
+	flush_disk_buffers();
+	log_read_start(len);
+	rc = cmp_data_log_rd_phys1_wr(len);
+	basic_test(rc == 0);
+	cleanup_test();
+}
+
+static void read_one_meg_after_write(void)
+{
+	int rc;
+	size_t len = ONE_MEG;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_write_start(len);
+	phys2_write_start(len);
+	flush_disk_buffers();
+	log_read_start(len);
+	rc = cmp_data_log_rd_phys1_wr(len);
+	basic_test(rc == 0);
+	cleanup_test();
+}
+
+static void write_one_sector_check_phys1_crc(void)
+{
+	int rc;
+	size_t len = ONE_SECTOR;
+
+	init_test();
+	log_fill_buffer(len);
+	log_write_start(len);
+	flush_disk_buffers();
+	phys1_read_start(len);
+	rc = cmp_crc_log_wr_phys1_rd(len);
+	basic_test(rc == 0);
+	cleanup_test();
+}
+
+static void write_one_page_check_phys1_crc(void)
+{
+	int rc;
+	size_t len = ONE_PAGE;
+
+	init_test();
+	log_fill_buffer(len);
+	log_write_start(len);
+	flush_disk_buffers();
+	phys1_read_start(len);
+	rc = cmp_crc_log_wr_phys1_rd(len);
+	basic_test(rc == 0);
+	cleanup_test();
+}
+
+static void write_two_pages_check_phys1_crc(void)
+{
+	int rc;
+	size_t len = TWO_PAGES;
+
+	init_test();
+	log_fill_buffer(len);
+	log_write_start(len);
+	flush_disk_buffers();
+	phys1_read_start(len);
+	rc = cmp_crc_log_wr_phys1_rd(len);
+	basic_test(rc == 0);
+	cleanup_test();
+}
+
+static void write_one_meg_check_phys1_crc(void)
+{
+	int rc;
+	size_t len = ONE_MEG;
+
+	init_test();
+	log_fill_buffer(len);
+	log_write_start(len);
+	flush_disk_buffers();
+	phys1_read_start(len);
+	rc = cmp_crc_log_wr_phys1_rd(len);
+	basic_test(rc == 0);
+	cleanup_test();
+}
+
+static void write_one_sector_check_phys_crc(void)
+{
+	int rc1, rc2;
+	size_t len = ONE_SECTOR;
+
+	init_test();
+	log_fill_buffer(len);
+	log_write_start(len);
+	flush_disk_buffers();
+	phys1_read_start(len);
+	phys2_read_start(len);
+	rc1 = cmp_crc_log_wr_phys1_rd(len);
+	rc2 = cmp_crc_log_wr_phys2_rd(len);
+	basic_test(rc1 == 0 && rc2 == 0);
+	cleanup_test();
+}
+
+static void write_one_page_check_phys_crc(void)
+{
+	int rc1, rc2;
+	size_t len = ONE_PAGE;
+
+	init_test();
+	log_fill_buffer(len);
+	log_write_start(len);
+	flush_disk_buffers();
+	phys1_read_start(len);
+	phys2_read_start(len);
+	rc1 = cmp_crc_log_wr_phys1_rd(len);
+	rc2 = cmp_crc_log_wr_phys2_rd(len);
+	basic_test(rc1 == 0 && rc2 == 0);
+	cleanup_test();
+}
+
+static void write_two_pages_check_phys_crc(void)
+{
+	int rc1, rc2;
+	size_t len = TWO_PAGES;
+
+	init_test();
+	log_fill_buffer(len);
+	log_write_start(len);
+	flush_disk_buffers();
+	phys1_read_start(len);
+	phys2_read_start(len);
+	rc1 = cmp_crc_log_wr_phys1_rd(len);
+	rc2 = cmp_crc_log_wr_phys2_rd(len);
+	basic_test(rc1 == 0 && rc2 == 0);
+	cleanup_test();
+}
+
+static void write_one_meg_check_phys_crc(void)
+{
+	int rc1, rc2;
+	size_t len = ONE_MEG;
+
+	init_test();
+	log_fill_buffer(len);
+	log_write_start(len);
+	flush_disk_buffers();
+	phys1_read_start(len);
+	phys2_read_start(len);
+	rc1 = cmp_crc_log_wr_phys1_rd(len);
+	rc2 = cmp_crc_log_wr_phys2_rd(len);
+	basic_test(rc1 == 0 && rc2 == 0);
+	cleanup_test();
+}
+
+static void corrupt_read_correct_one_sector_disk1(void)
+{
+	int rc_data, rc_crc;
+	size_t len = ONE_SECTOR;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_corrupt_and_write_start(len, 1);
+	phys2_write_start(len);
+	flush_disk_buffers();
+	log_read_start(len);
+	rc_data = cmp_data_log_rd_phys2_wr(len);
+	rc_crc = cmp_crc_log_rd_phys2_wr(len);
+	basic_test(rc_data == 0 && rc_crc == 0);
+	cleanup_test();
+}
+
+static void corrupt_read_correct_one_sector_in_page_disk1(void)
+{
+	int rc_data, rc_crc;
+	size_t len = ONE_PAGE;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_corrupt_and_write_start(len, 1);
+	phys2_write_start(len);
+	flush_disk_buffers();
+	log_read_start(len);
+	rc_data = cmp_data_log_rd_phys2_wr(len);
+	rc_crc = cmp_crc_log_rd_phys2_wr(len);
+	basic_test(rc_data == 0 && rc_crc == 0);
+	cleanup_test();
+}
+
+static void corrupt_read_correct_one_page_disk1(void)
+{
+	int rc_data, rc_crc;
+	size_t len = ONE_PAGE;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_corrupt_and_write_start(len, ONE_PAGE / ONE_SECTOR);
+	phys2_write_start(len);
+	flush_disk_buffers();
+	log_read_start(len);
+	rc_data = cmp_data_log_rd_phys2_wr(len);
+	rc_crc = cmp_crc_log_rd_phys2_wr(len);
+	basic_test(rc_data == 0 && rc_crc == 0);
+	cleanup_test();
+}
+
+static void corrupt_read_correct_ten_pages_in_one_meg_disk1(void)
+{
+	int rc_data, rc_crc;
+	size_t len = ONE_MEG;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_corrupt_and_write_start(len, TEN_PAGES / ONE_SECTOR);
+	phys2_write_start(len);
+	flush_disk_buffers();
+	log_read_start(len);
+	rc_data = cmp_data_log_rd_phys2_wr(len);
+	rc_crc = cmp_crc_log_rd_phys2_wr(len);
+	basic_test(rc_data == 0 && rc_crc == 0);
+	cleanup_test();
+}
+
+static void corrupt_read_correct_one_meg_disk1(void)
+{
+	int rc_data, rc_crc;
+	size_t len = ONE_MEG;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_corrupt_and_write_start(len, ONE_MEG / ONE_SECTOR);
+	phys2_write_start(len);
+	flush_disk_buffers();
+	log_read_start(len);
+	rc_data = cmp_data_log_rd_phys2_wr(len);
+	rc_crc = cmp_crc_log_rd_phys2_wr(len);
+	basic_test(rc_data == 0 && rc_crc == 0);
+	cleanup_test();
+}
+
+static void recover_one_sector_disk1(void)
+{
+	int rc_data, rc_crc;
+	size_t len = ONE_SECTOR;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_corrupt_and_write_start(len, 1);
+	phys2_write_start(len);
+	flush_disk_buffers();
+	log_read_start(len);
+	flush_disk_buffers();
+	phys1_read_start(len);
+	rc_data = cmp_data_log_rd_phys1_rd(len);
+	rc_crc = cmp_crc_log_rd_phys1_rd(len);
+	basic_test(rc_data == 0 && rc_crc == 0);
+	cleanup_test();
+}
+
+static void recover_one_sector_in_page_disk1(void)
+{
+	int rc_data, rc_crc;
+	size_t len = ONE_PAGE;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_corrupt_and_write_start(len, 1);
+	phys2_write_start(len);
+	flush_disk_buffers();
+	log_read_start(len);
+	flush_disk_buffers();
+	phys1_read_start(len);
+	rc_data = cmp_data_log_rd_phys1_rd(len);
+	rc_crc = cmp_crc_log_rd_phys1_rd(len);
+	basic_test(rc_data == 0 && rc_crc == 0);
+	cleanup_test();
+}
+
+static void recover_one_page_disk1(void)
+{
+	int rc_data, rc_crc;
+	size_t len = ONE_PAGE;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_corrupt_and_write_start(len, ONE_PAGE / ONE_SECTOR);
+	phys2_write_start(len);
+	flush_disk_buffers();
+	log_read_start(len);
+	flush_disk_buffers();
+	phys1_read_start(len);
+	rc_data = cmp_data_log_rd_phys1_rd(len);
+	rc_crc = cmp_crc_log_rd_phys1_rd(len);
+	basic_test(rc_data == 0 && rc_crc == 0);
+	cleanup_test();
+}
+
+static void recover_ten_page_in_one_meg_disk1(void)
+{
+	int rc_data, rc_crc;
+	size_t len = ONE_MEG;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_corrupt_and_write_start(len, TEN_PAGES / ONE_SECTOR);
+	phys2_write_start(len);
+	flush_disk_buffers();
+	log_read_start(len);
+	flush_disk_buffers();
+	phys1_read_start(len);
+	rc_data = cmp_data_log_rd_phys1_rd(len);
+	rc_crc = cmp_crc_log_rd_phys1_rd(len);
+	basic_test(rc_data == 0 && rc_crc == 0);
+	cleanup_test();
+}
+
+static void recover_one_meg_disk1(void)
+{
+	int rc_data, rc_crc;
+	size_t len = ONE_MEG;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_corrupt_and_write_start(len, ONE_MEG / ONE_SECTOR);
+	phys2_write_start(len);
+	flush_disk_buffers();
+	log_read_start(len);
+	flush_disk_buffers();
+	phys1_read_start(len);
+	rc_data = cmp_data_log_rd_phys1_rd(len);
+	rc_crc = cmp_crc_log_rd_phys1_rd(len);
+	basic_test(rc_data == 0 && rc_crc == 0);
+	cleanup_test();
+}
+
+static void corrupt_read_correct_one_sector_disk2(void)
+{
+	int rc_data, rc_crc;
+	size_t len = ONE_SECTOR;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_write_start(len);
+	phys2_corrupt_and_write_start(len, 1);
+	flush_disk_buffers();
+	log_read_start(len);
+	rc_data = cmp_data_log_rd_phys1_wr(len);
+	rc_crc = cmp_crc_log_rd_phys1_wr(len);
+	basic_test(rc_data == 0 && rc_crc == 0);
+	cleanup_test();
+}
+
+static void corrupt_read_correct_one_sector_in_page_disk2(void)
+{
+	int rc_data, rc_crc;
+	size_t len = ONE_PAGE;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_write_start(len);
+	phys2_corrupt_and_write_start(len, 1);
+	flush_disk_buffers();
+	log_read_start(len);
+	rc_data = cmp_data_log_rd_phys1_wr(len);
+	rc_crc = cmp_crc_log_rd_phys1_wr(len);
+	basic_test(rc_data == 0 && rc_crc == 0);
+	cleanup_test();
+}
+
+static void corrupt_read_correct_one_page_disk2(void)
+{
+	int rc_data, rc_crc;
+	size_t len = ONE_PAGE;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_write_start(len);
+	phys2_corrupt_and_write_start(len, ONE_PAGE / ONE_SECTOR);
+	flush_disk_buffers();
+	log_read_start(len);
+	rc_data = cmp_data_log_rd_phys1_wr(len);
+	rc_crc = cmp_crc_log_rd_phys1_wr(len);
+	basic_test(rc_data == 0 && rc_crc == 0);
+	cleanup_test();
+}
+
+static void corrupt_read_correct_ten_pages_in_one_meg_disk2(void)
+{
+	int rc_data, rc_crc;
+	size_t len = ONE_MEG;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_write_start(len);
+	phys2_corrupt_and_write_start(len, TEN_PAGES / ONE_SECTOR);
+	flush_disk_buffers();
+	log_read_start(len);
+	rc_data = cmp_data_log_rd_phys1_wr(len);
+	rc_crc = cmp_crc_log_rd_phys1_wr(len);
+	basic_test(rc_data == 0 && rc_crc == 0);
+	flush_disk_buffers();
+	cleanup_test();
+}
+
+static void corrupt_read_correct_one_meg_disk2(void)
+{
+	int rc_data, rc_crc;
+	size_t len = ONE_MEG;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_write_start(len);
+	phys2_corrupt_and_write_start(len, ONE_MEG / ONE_SECTOR);
+	flush_disk_buffers();
+	log_read_start(len);
+	rc_data = cmp_data_log_rd_phys1_wr(len);
+	rc_crc = cmp_crc_log_rd_phys1_wr(len);
+	basic_test(rc_data == 0 && rc_crc == 0);
+	cleanup_test();
+}
+
+static void recover_one_sector_disk2(void)
+{
+	int rc_data, rc_crc;
+	size_t len = ONE_SECTOR;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_write_start(len);
+	phys2_corrupt_and_write_start(len, 1);
+	flush_disk_buffers();
+	log_read_start(len);
+	flush_disk_buffers();
+	phys2_read_start(len);
+	rc_data = cmp_data_log_rd_phys2_rd(len);
+	rc_crc = cmp_crc_log_rd_phys2_rd(len);
+	basic_test(rc_data == 0 && rc_crc == 0);
+	cleanup_test();
+}
+
+static void recover_one_sector_in_page_disk2(void)
+{
+	int rc_data, rc_crc;
+	size_t len = ONE_PAGE;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_write_start(len);
+	phys2_corrupt_and_write_start(len, 1);
+	flush_disk_buffers();
+	log_read_start(len);
+	flush_disk_buffers();
+	phys2_read_start(len);
+	rc_data = cmp_data_log_rd_phys2_rd(len);
+	rc_crc = cmp_crc_log_rd_phys2_rd(len);
+	basic_test(rc_data == 0 && rc_crc == 0);
+	cleanup_test();
+}
+
+static void recover_one_page_disk2(void)
+{
+	int rc_data, rc_crc;
+	size_t len = ONE_PAGE;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_write_start(len);
+	phys2_corrupt_and_write_start(len, ONE_PAGE / ONE_SECTOR);
+	flush_disk_buffers();
+	log_read_start(len);
+	flush_disk_buffers();
+	phys2_read_start(len);
+	rc_data = cmp_data_log_rd_phys2_rd(len);
+	rc_crc = cmp_crc_log_rd_phys2_rd(len);
+	basic_test(rc_data == 0 && rc_crc == 0);
+	cleanup_test();
+}
+
+static void recover_ten_page_in_one_meg_disk2(void)
+{
+	int rc_data, rc_crc;
+	size_t len = ONE_MEG;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_write_start(len);
+	phys2_corrupt_and_write_start(len, TEN_PAGES / ONE_SECTOR);
+	flush_disk_buffers();
+	log_read_start(len);
+	flush_disk_buffers();
+	phys2_read_start(len);
+	rc_data = cmp_data_log_rd_phys2_rd(len);
+	rc_crc = cmp_crc_log_rd_phys2_rd(len);
+	basic_test(rc_data == 0 && rc_crc == 0);
+	cleanup_test();
+}
+
+static void recover_one_meg_disk2(void)
+{
+	int rc_data, rc_crc;
+	size_t len = ONE_MEG;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_write_start(len);
+	phys2_corrupt_and_write_start(len, ONE_MEG / ONE_SECTOR);
+	flush_disk_buffers();
+	log_read_start(len);
+	flush_disk_buffers();
+	phys2_read_start(len);
+	rc_data = cmp_data_log_rd_phys2_rd(len);
+	rc_crc = cmp_crc_log_rd_phys2_rd(len);
+	basic_test(rc_data == 0 && rc_crc == 0);
+	cleanup_test();
+}
+
+static void dual_error(void)
+{
+	ssize_t n;
+	size_t len = ONE_SECTOR;
+
+	init_test();
+	phys_fill_buffer(len);
+	phys1_corrupt_and_write_start(len, 1);
+	phys2_corrupt_and_write_start(len, 1);
+	flush_disk_buffers();
+	n = log_read_start(len);
+	basic_test(n <= 0);
+	cleanup_test();
+}
+
+struct run_test_t test_array[] = {
+	{ open_logical, "open(" LOGICAL_DISK_NAME ")", 4 },
+	{ close_logical, "close(" LOGICAL_DISK_NAME ")", 4 },
+	{ use_after_close_invalid, "use after close is invalid", 4 },
+	{ lseek_logical, "lseek(" LOGICAL_DISK_NAME ")", 4 },
+	{ read_one_sector_start, "read one sector from the start", 5 },
+	{ read_one_sector_middle, "read one sector from the middle", 5 },
+	{ read_one_sector_end, "read one sector from the end", 5 },
+	{ write_one_sector_start, "write one sector from the start", 5 },
+	{ write_one_sector_middle, "write one sector from the middle", 5 },
+	{ write_one_sector_end, "write one sector from the end", 5 },
+	{ read_one_page_start, "read one page from the start", 5 },
+	{ read_one_page_middle, "read one page from the middle", 5 },
+	{ read_one_page_end, "read one page from the end", 5 },
+	{ write_one_page_start, "write one page from the start", 5 },
+	{ write_one_page_middle, "write one page from the middle", 5 },
+	{ write_one_page_end, "write one page from the end", 5 },
+	{ read_two_pages_start, "read two pages from the start", 5 },
+	{ read_two_pages_middle, "read two pages from the middle", 5 },
+	{ read_two_pages_end, "read two pages from the end", 5 },
+	{ write_two_pages_start, "write two pages from the start", 5 },
+	{ write_two_pages_middle, "write two pages from the middle", 5 },
+	{ write_two_pages_end, "write two pages from the end", 5 },
+	{ read_one_meg_start, "read 1MB from the start", 5 },
+	{ read_one_meg_middle, "read 1MB from the middle", 5 },
+	{ read_one_meg_end, "read 1MB from the end", 5 },
+	{ write_one_meg_start, "write 1MB from the start", 5 },
+	{ write_one_meg_middle, "write 1MB from the middle", 5 },
+	{ write_one_meg_end, "write 1MB from the end", 5 },
+	{ read_boundary_one_sector, "read one sector outside boundary", 7 },
+	{ read_boundary_one_page, "read one page with contents outside boundary", 7 },
+	{ read_boundary_two_pages, "read two pages with contents outside boundary", 7 },
+	{ read_boundary_one_meg, "read 1MB with contents outside boundary", 7 },
+	{ write_boundary_one_sector, "write one sector outside boundary", 7 },
+	{ write_boundary_one_page, "write one page with contents outside boundary", 7 },
+	{ write_boundary_two_pages, "write two pages with contents outside boundary", 7 },
+	{ write_boundary_one_meg, "write 1MB with contents outside boundary", 7 },
+	{ memory_is_freed, "check memory is freed", 24 },
+	{ write_one_sector_check_phys1, "write one sector and check disk1 (no CRC check)", 15 },
+	{ write_one_page_check_phys1, "write one page and check disk1 (no CRC check)", 15 },
+	{ write_two_pages_check_phys1, "write two pages and check disk1 (no CRC check)", 15 },
+	{ write_one_meg_check_phys1, "write 1MB and check disk1 (no CRC check)", 15 },
+	{ write_one_sector_check_phys, "write one sector and check disks (no CRC check)", 15 },
+	{ write_one_page_check_phys, "write one page and check disks (no CRC check)", 15 },
+	{ write_two_pages_check_phys, "write two pages and check disks (no CRC check)", 15 },
+	{ write_one_meg_check_phys, "write 1MB and check disks (no CRC check)", 15 },
+	{ read_one_sector_after_write, "read one sector after physical write (correct CRC)", 16 },
+	{ read_one_page_after_write, "read one page after physical write (correct CRC)", 16 },
+	{ read_two_pages_after_write, "read two pages after physical write (correct CRC)", 16 },
+	{ read_one_meg_after_write, "read 1MB after physical write (correct CRC)", 16 },
+	{ write_one_sector_check_phys1_crc, "write one sector and check disk1 (do CRC check)", 16 },
+	{ write_one_page_check_phys1_crc, "write one page and check disk1 (do CRC check)", 16 },
+	{ write_two_pages_check_phys1_crc, "write two pages and check disk1 (do CRC check)", 16 },
+	{ write_one_meg_check_phys1_crc, "write 1MB and check disk1 (do CRC check)", 16 },
+	{ write_one_sector_check_phys_crc, "write one sector and check disks (do CRC check)", 16 },
+	{ write_one_page_check_phys_crc, "write one page and check disks (do CRC check)", 16 },
+	{ write_two_pages_check_phys_crc, "write two pages and check disks (do CRC check)", 16 },
+	{ write_one_meg_check_phys_crc, "write 1MB and check disks (do CRC check)", 16 },
+	{ corrupt_read_correct_one_sector_disk1, "read corrected one sector error from disk1", 18 },
+	{ corrupt_read_correct_one_sector_in_page_disk1, "read corrected one sector in page error from disk1", 18 },
+	{ corrupt_read_correct_one_page_disk1, "read corrected one page error from disk1", 18 },
+	{ corrupt_read_correct_ten_pages_in_one_meg_disk1, "read corrected ten pages error in one meg from disk1", 18 },
+	{ corrupt_read_correct_one_meg_disk1, "read corrected one meg error from disk1", 18 },
+	{ recover_one_sector_disk1, "recover one sector error from disk1", 18 },
+	{ recover_one_sector_in_page_disk1, "recover one sector error in one page from disk1", 18 },
+	{ recover_one_page_disk1, "recover one page filled with errors from disk1", 18 },
+	{ recover_ten_page_in_one_meg_disk1, "recover ten pages error in 1MB from disk1", 18 },
+	{ recover_one_meg_disk1, "recover 1MB filled with errors from disk1", 18 },
+	{ corrupt_read_correct_one_sector_disk2, "read corrected one sector error from disk2", 18 },
+	{ corrupt_read_correct_one_sector_in_page_disk2, "read corrected one sector in page error from disk2", 18 },
+	{ corrupt_read_correct_one_page_disk2, "read corrected one page error from disk2", 18 },
+	{ corrupt_read_correct_ten_pages_in_one_meg_disk2, "read corrected ten pages error in one meg from disk2", 18 },
+	{ corrupt_read_correct_one_meg_disk2, "read corrected one meg error from disk2", 18 },
+	{ recover_one_sector_disk2, "recover one sector error from disk2", 18 },
+	{ recover_one_sector_in_page_disk2, "recover one sector error in one page from disk2", 18 },
+	{ recover_one_page_disk2, "recover one page filled with errors from disk2", 18 },
+	{ recover_ten_page_in_one_meg_disk2, "recover ten pages error in 1MB from disk2", 18 },
+	{ recover_one_meg_disk2, "recover 1MB filled with errors from disk2", 18 },
+	{ dual_error, "signal error when both physical disks are corrupted", 12 },
+};
+size_t max_points = 900;
+
+/* Return number of tests in test_array. */
+size_t get_num_tests(void)
+{
+	return sizeof(test_array) / sizeof(test_array[0]);
+}
diff --git a/checker/4-stp-checker/Makefile b/checker/4-stp-checker/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..e0c2b42807f22f2a94bf684002d7fa6a86aa8891
--- /dev/null
+++ b/checker/4-stp-checker/Makefile
@@ -0,0 +1,17 @@
+objects = _test/stp_test.o
+
+.PHONY: all clean  _test_subdir_all _test_subdir_clean
+
+all: stp_test
+
+stp_test: _test_subdir_all $(objects)
+	$(CC) -Wall -g -m32 -static $(objects) -Wl,--whole-archive -lpthread -Wl,--no-whole-archive -o $@
+
+_test_subdir_all:
+	make -C _test
+
+clean: _test_subdir_clean
+	-rm -f stp_test *~
+
+_test_subdir_clean:
+	make -C _test clean
diff --git a/checker/4-stp-checker/README b/checker/4-stp-checker/README
new file mode 100644
index 0000000000000000000000000000000000000000..a1d04d84f8653e98d3812f0a9ecbfa26d64c5956
--- /dev/null
+++ b/checker/4-stp-checker/README
@@ -0,0 +1,87 @@
+= STP TEST SUITE ==
+
+Test suite for SO2 Tranport Protocol
+
+== FILES ==
+
+README
+	* this file
+
+Makefile
+
+_checker
+	* script to run all tests defined in _test/stp_test.c
+
+_test/Makefile
+	* test suite internal Makefile (creates necessary object files)
+
+_test/stp_test.c
+	* test suite for SO2 Transport Protocol
+
+_test/stp_test.h
+	* test suite header file
+
+_test/stp.h
+	* SO2 Transport Protocol header file (macros and structures)
+
+_test/test.h
+	* useful macros for testing
+
+_test/debug.h
+	* debugging macros
+
+_test/util.h
+	* useful macros for generic use (error processing)
+
+== BUILDING ==
+
+
+== RUNNING ==
+
+Copy your af_stp.ko module and _checker and stp_test
+to fsimg/root directory on your QEMU/KVM virtual machine.
+
+In order to run the test suite you can either use the _checker
+script or run the stp_test executable.
+
+The _checker script runs all tests and computes assignment grade:
+
+	./_checker
+
+In order to run a specific test pass the test number (1 .. 32) to the
+stp_test executable.
+
+	./stp_test 5
+
+== TESTS ==
+
+Tests are basically unit tests. A single function in the test_fun_array (see
+stp_test.c) is called each time the stp_test executable is invoked,
+testing a single functionality (and assuming previous tests have been run and
+passed).
+
+The EXIT_IF_FAIL macro (see test.h) is unnecessary since after each test, the
+program completes.
+
+Each test function follows the unit test pattern: initialization, action,
+evaluation. The test macro (see test.h) is invoked at the end of each test
+for evaluating and grading the test.
+
+== DEBUGGING ==
+
+The debug.h header file consists of several macros useful for debugging
+(dprintf, dlog). There are multiple uses of these macros throughout the above
+files.
+
+In order to turn debug messages on, you must define the DEBUG macro, either in
+a header file, or, I suggest, in the Makefile. The LOG_LEVEL macro limits the
+log message types that are to be printed, by default LOG_WARNING (see enum in
+debug.h). You may redefine it in a header file or in the Makefile.
+
+Rapid enabling of debug messages is achieved by commenting out the CPPFLAGS
+line in the Makefile. It turns on debugging and enables all log messages
+(LOG_DEBUG).
+
+== OTHER ==
+
+srand48() and drand48() are used for generating random numbers.
diff --git a/checker/4-stp-checker/_checker b/checker/4-stp-checker/_checker
new file mode 100755
index 0000000000000000000000000000000000000000..810e77f5e98e5337f30ad6e54d707baacaca1f4f
--- /dev/null
+++ b/checker/4-stp-checker/_checker
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+first_test=1
+last_test=32
+executable=stp_test
+
+for i in $(seq $first_test $last_test); do
+    ./"$executable" $i
+done | tee results.txt
+
+cat results.txt | grep '\[.*\]$' | awk -F '[] /[]+' '
+BEGIN {
+    sum=0
+}
+
+{
+    sum += $2;
+}
+
+END {
+    printf "\n%66s  [%d/100]\n", "Total:", sum;
+}'
+
+rm -f results.txt
diff --git a/checker/4-stp-checker/_test/Makefile b/checker/4-stp-checker/_test/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..d5074dd464a5f5fa80cf42324e336ebdb975f529
--- /dev/null
+++ b/checker/4-stp-checker/_test/Makefile
@@ -0,0 +1,11 @@
+#CPPFLAGS = -DDEBUG -DLOG_LEVEL=LOG_DEBUG
+CFLAGS = -Wall -g -m32
+
+.PHONY: all clean
+
+all: stp_test.o
+
+stp_test.o: stp_test.c stp_test.h stp.h test.h util.h debug.h
+
+clean:
+	-rm -f *~ *.o
diff --git a/checker/4-stp-checker/_test/debug.h b/checker/4-stp-checker/_test/debug.h
new file mode 100644
index 0000000000000000000000000000000000000000..a54e96226471814a6f8595bc0a3c59cce37c5210
--- /dev/null
+++ b/checker/4-stp-checker/_test/debug.h
@@ -0,0 +1,77 @@
+/*
+ * debugging macros
+ *    heavily inspired by previous work and Internet resources
+ *
+ * uses C99 variadic macros
+ * uses non-standard usage of the token-paste operator (##) for
+ *   removing the comma symbol (,) when not followed by a token
+ * uses non-standard __FUNCTION__ macro (MSVC doesn't support __func__)
+ * tested on gcc 4.4.5 and Visual Studio 2008 (9.0), compiler version 15.00
+ *
+ * Razvan Deaconescu, razvan.deaconescu@cs.pub.ro
+ */
+
+#ifndef DEBUG_H_
+#define DEBUG_H_	1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+
+/* log levels */
+enum {
+	LOG_EMERG = 1,
+	LOG_ALERT,
+	LOG_CRIT,
+	LOG_ERR,
+	LOG_WARNING,
+	LOG_NOTICE,
+	LOG_INFO,
+	LOG_DEBUG
+};
+
+/*
+ * initialize default loglevel (for dlog)
+ * may be redefined in the including code
+ */
+
+#ifndef LOG_LEVEL
+#define LOG_LEVEL	LOG_WARNING
+#endif
+
+/*
+ * define DEBUG macro as a compiler option:
+ *    -DDEBUG for GCC
+ *    /DDEBUG for MSVC
+ */
+
+#if defined DEBUG
+#define dprintf(format, ...)					\
+	fprintf(stderr, " [%s(), %s:%u] " format,		\
+			__FUNCTION__, __FILE__, __LINE__,	\
+			##__VA_ARGS__)
+#else
+#define dprintf(format, ...)					\
+	do {							\
+	} while (0)
+#endif
+
+#if defined DEBUG
+#define dlog(level, format, ...)				\
+	do {							\
+		if (level <= LOG_LEVEL)				\
+			dprintf(format, ##__VA_ARGS__);		\
+	} while (0)
+#else
+#define dlog(level, format, ...)				\
+	do {							\
+	} while (0)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/checker/4-stp-checker/_test/stp.h b/checker/4-stp-checker/_test/stp.h
new file mode 100644
index 0000000000000000000000000000000000000000..838f9936bf55c535d5e6ed640bb5e617effd1a26
--- /dev/null
+++ b/checker/4-stp-checker/_test/stp.h
@@ -0,0 +1,51 @@
+/*
+ * SO2 Transport Protocol
+ */
+
+#ifndef STP_H_
+#define STP_H_	1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <linux/types.h>
+
+/* STP reuses the defines of ancient protocols like Econet and Xerox PUP
+ * because adding a new protocol would involve patching the kernel, which we
+ * don't want to do and besides that, they are probably not used anymore.
+ */
+#define AF_STP		19
+#define PF_STP		AF_STP
+#define ETH_P_STP	0x0a00
+
+struct stp_hdr {
+	__be16		dst;		/* Destination port */
+	__be16		src;		/* Source port */
+	__be16		len;		/* Total length, including header */
+	__u8		flags;		/* */
+	__u8		csum;		/* xor of all bytes, including header */
+};
+
+struct sockaddr_stp {
+	unsigned short	sas_family;	/* Always AF_STP */
+	int		sas_ifindex;	/* Interface index */
+	__be16		sas_port;	/* Port */
+	__u8		sas_addr[6];	/* MAC address */
+};
+
+/* STP protocol name; used as identifier in /proc/net/protocols */
+#define STP_PROTO_NAME			"STP"
+
+/*
+ * STP uses proc interface to communicate statistical information to
+ * user space (in /proc/net/).
+ */
+#define STP_PROC_NET_FILENAME		"stp_stats"
+#define STP_PROC_FULL_FILENAME		"/proc/net/" STP_PROC_NET_FILENAME
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* STP_H_ */
diff --git a/checker/4-stp-checker/_test/stp_test.c b/checker/4-stp-checker/_test/stp_test.c
new file mode 100644
index 0000000000000000000000000000000000000000..d6c729e344e634a0a90eced63918d7953cb8faf6
--- /dev/null
+++ b/checker/4-stp-checker/_test/stp_test.c
@@ -0,0 +1,1331 @@
+/*
+ * SO2 Transport Protocol - test suite
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <assert.h>
+#include <time.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ether.h>
+#include <net/if.h>
+#include <assert.h>
+#include <sys/wait.h>
+#include <semaphore.h>
+#include <fcntl.h>
+
+#include "test.h"
+#include "debug.h"
+#include "util.h"
+
+#include "stp.h"
+#include "stp_test.h"
+
+#define SSA			struct sockaddr
+#define BUFLEN			32
+
+/* declared in test.h; used for printing information in test macro */
+int max_points = 100;
+
+/* values read from STP_PROC_FULL_FILENAME */
+static int rx_pkts, hdr_err, csum_err, no_sock, no_buffs, tx_pkts;
+
+enum socket_action {
+	ACTION_SENDTO,
+	ACTION_SENDMSG,
+	ACTION_SEND,
+	ACTION_SENDTO_PING_PONG,
+	ACTION_SENDMSG_PING_PONG,
+	ACTION_SEND_PING_PONG,
+};
+
+/*
+ * Do initialization for STP test functions.
+ */
+
+static void init_test(void)
+{
+	system("insmod " MODULE_FILENAME);
+}
+
+/*
+ * Do cleanup for STP test functions.
+ */
+
+static void cleanup_test(void)
+{
+	system("rmmod " MODULE_NAME);
+}
+
+/*
+ * Check for successful module insertion and removal from the kernel.
+ */
+
+static void test_insmod_rmmod(void)
+{
+	int rc;
+
+	rc = system("insmod " MODULE_FILENAME);
+	test("test_insmod", rc == 0, 1);
+
+	rc = system("rmmod " MODULE_NAME);
+	test("test_rmmod", rc == 0, 1);
+
+	rc = system("insmod " MODULE_FILENAME);
+	test(__FUNCTION__, rc == 0, 1);
+
+	system("rmmod " MODULE_NAME);
+}
+
+/*
+ * Check /proc/net/protocols for STP protocol. Grep for line starting with
+ * the string identified by STP_PROTO_NAME.
+ */
+
+static void test_proto_name_exists_after_insmod(void)
+{
+	int rc;
+
+	init_test();
+
+	rc = system("grep '^" STP_PROTO_NAME "' /proc/net/protocols > /dev/null 2>&1");
+	test(__FUNCTION__, rc == 0, 2);
+
+	cleanup_test();
+}
+
+/*
+ * STP entry in /proc/net/protocols is deleted when module is removed.
+ */
+
+static void test_proto_name_inexistent_after_rmmod(void)
+{
+	int rc;
+
+	init_test();
+	cleanup_test();
+
+	rc = system("grep '^" STP_PROTO_NAME "' /proc/net/protocols > /dev/null 2>&1");
+	test(__FUNCTION__, rc != 0, 2);
+}
+
+/*
+ * Check for proc entry for STP statistics.
+ */
+
+static void test_proc_entry_exists_after_insmod(void)
+{
+	int rc;
+
+	init_test();
+
+	rc = access(STP_PROC_FULL_FILENAME, F_OK);
+	test(__FUNCTION__, rc == 0, 2);
+
+	cleanup_test();
+}
+
+/*
+ * STP statistics file in /proc/net/ is deleted when module is removed.
+ */
+
+static void test_proc_entry_inexistent_after_rmmod(void)
+{
+	int rc;
+
+	init_test();
+	cleanup_test();
+
+	rc = system("file " STP_PROC_FULL_FILENAME " > /dev/null 2>&1");
+	test(__FUNCTION__, rc != 0, 2);
+}
+
+/*
+ * Call socket(2) with proper arguments for creating an AF_STP socket.
+ */
+
+static void test_socket(void)
+{
+	int s;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+	test(__FUNCTION__, s > 0, 5);
+
+	close(s);
+	cleanup_test();
+}
+
+/*
+ * Create two AF_STP sockets using socket(2).
+ */
+
+static void test_two_sockets(void)
+{
+	int s1, s2;
+
+	init_test();
+
+	s1 = socket(AF_STP, SOCK_DGRAM, 0);
+	s2 = socket(AF_STP, SOCK_DGRAM, 0);
+	test(__FUNCTION__, s1 > 0 && s2 > 0 && s1 != s2, 2);
+
+	close(s1);
+	close(s2);
+	cleanup_test();
+}
+
+/*
+ * Pass bad socket type argument to socket(2) (second argument).
+ * Call should fail.
+ */
+
+static void test_socket_bad_socket_type(void)
+{
+	int s;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_STREAM, 0);
+	test(__FUNCTION__, s < 0, 1);
+
+	close(s);
+	cleanup_test();
+}
+
+/*
+ * Pass bad protocol argument to socket(2) (third argument).
+ * Call should fail.
+ */
+
+static void test_socket_bad_protocol(void)
+{
+	int s;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, IPPROTO_TCP);
+	test(__FUNCTION__, s < 0, 1);
+
+	close(s);
+	cleanup_test();
+}
+
+/*
+ * Close open socket using close(2).
+ */
+
+static void test_close(void)
+{
+	int s;
+	int rc;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	rc = close(s);
+	test(__FUNCTION__, rc == 0, 2);
+
+	cleanup_test();
+}
+
+/*
+ * Pass closed socket descriptor to close(2). Call should fail.
+ */
+
+static void test_close_closed_socket(void)
+{
+	int s;
+	int rc;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	close(s);
+	rc = close(s);
+
+	test(__FUNCTION__, rc < 0, 2);
+
+	cleanup_test();
+}
+
+/*
+ * Bind socket to proper address. Use "all" interface.
+ */
+
+static void test_bind(void)
+{
+	int s;
+	int rc;
+	struct sockaddr_stp sas;
+	const unsigned short port = 12345;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas.sas_family = AF_STP;
+	sas.sas_port = htons(port);
+	sas.sas_ifindex = 0;
+	rc = bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp));
+
+	test(__FUNCTION__, rc == 0, 5);
+
+	close(s);
+	cleanup_test();
+}
+
+/*
+ * Bind socket to proper address. Use "eth0" interface.
+ */
+
+static void test_bind_eth0(void)
+{
+	int s;
+	int rc;
+	struct sockaddr_stp sas;
+	const unsigned short port = 12345;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas.sas_family = AF_STP;
+	sas.sas_port = htons(port);
+	sas.sas_ifindex = if_nametoindex("eth0");
+	rc = bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp));
+
+	test(__FUNCTION__, rc == 0, 2);
+
+	close(s);
+	cleanup_test();
+}
+
+/*
+ * Use bind(2) on two AF_STP sockets.
+ */
+
+static void test_two_binds(void)
+{
+	int s1, s2;
+	int rc1, rc2;
+	struct sockaddr_stp sas1, sas2;
+	const unsigned short port1 = 12345, port2 = 54321;
+
+	init_test();
+
+	s1 = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas1.sas_family = AF_STP;
+	sas1.sas_port = htons(port1);
+	sas1.sas_ifindex = 0;
+	rc1 = bind(s1, (struct sockaddr *) &sas1, sizeof(struct sockaddr_stp));
+
+	s2 = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas2.sas_family = AF_STP;
+	sas2.sas_port = htons(port2);
+	sas2.sas_ifindex = 0;
+	rc2 = bind(s2, (struct sockaddr *) &sas2, sizeof(struct sockaddr_stp));
+
+	test(__FUNCTION__, rc1 == 0 && rc2 == 0, 2);
+
+	close(s1); close(s2);
+	cleanup_test();
+}
+
+/*
+ * Pass bad address to bind(2) (second argument).
+ * Call should fail.
+ */
+
+static void test_bind_bad_address(void)
+{
+	int s;
+	int rc;
+	struct sockaddr_stp sas;
+	const unsigned short port = 12345;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas.sas_family = AF_INET;	/* invalid */
+	sas.sas_port = htons(port);
+	sas.sas_ifindex = 0;
+	rc = bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp));
+
+	test(__FUNCTION__, rc != 0, 1);
+
+	close(s);
+	cleanup_test();
+}
+
+/*
+ * Use bind(2) on two AF_STP sockets using same port and "all" interface.
+ * Call should fail.
+ */
+
+static void test_two_binds_same_if(void)
+{
+	int s1, s2;
+	int rc1, rc2;
+	struct sockaddr_stp sas1, sas2;
+	const unsigned short port = 12345;
+
+	init_test();
+
+	s1 = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas1.sas_family = AF_STP;
+	sas1.sas_port = htons(port);
+	sas1.sas_ifindex = 0;
+	rc1 = bind(s1, (struct sockaddr *) &sas1, sizeof(struct sockaddr_stp));
+
+	s2 = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas2.sas_family = AF_STP;
+	sas2.sas_port = htons(port);
+	sas2.sas_ifindex = 0;
+	rc2 = bind(s2, (struct sockaddr *) &sas2, sizeof(struct sockaddr_stp));
+
+	test(__FUNCTION__, rc1 == 0 && rc2 < 0, 2);
+
+	close(s1); close(s2);
+	cleanup_test();
+}
+
+/*
+ * Use bind(2) on two AF_STP sockets using same port and same interface.
+ * Call should fail.
+ */
+
+static void test_two_binds_same_if_eth0(void)
+{
+	int s1, s2;
+	int rc1, rc2;
+	struct sockaddr_stp sas1, sas2;
+	const unsigned short port = 12345;
+
+	init_test();
+
+	s1 = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas1.sas_family = AF_STP;
+	sas1.sas_port = htons(port);
+	sas1.sas_ifindex = if_nametoindex("eth0");
+	rc1 = bind(s1, (struct sockaddr *) &sas1, sizeof(struct sockaddr_stp));
+
+	s2 = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas2.sas_family = AF_STP;
+	sas2.sas_port = htons(port);
+	sas2.sas_ifindex = if_nametoindex("eth0");
+	rc2 = bind(s2, (struct sockaddr *) &sas2, sizeof(struct sockaddr_stp));
+
+	test(__FUNCTION__, rc1 == 0 && rc2 < 0, 2);
+
+	close(s1); close(s2);
+	cleanup_test();
+}
+
+/*
+ * Use bind(2) on two AF_STP sockets using same port and "all" interface and
+ * "eth0".
+ * Call should fail.
+ */
+
+static void test_two_binds_same_if_all_eth0(void)
+{
+	int s1, s2;
+	int rc1, rc2;
+	struct sockaddr_stp sas1, sas2;
+	const unsigned short port = 12345;
+
+	init_test();
+
+	s1 = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas1.sas_family = AF_STP;
+	sas1.sas_port = htons(port);
+	sas1.sas_ifindex = 0;
+	rc1 = bind(s1, (struct sockaddr *) &sas1, sizeof(struct sockaddr_stp));
+
+	s2 = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas2.sas_family = AF_STP;
+	sas2.sas_port = htons(port);
+	sas2.sas_ifindex = if_nametoindex("eth0");
+	rc2 = bind(s2, (struct sockaddr *) &sas2, sizeof(struct sockaddr_stp));
+
+	test(__FUNCTION__, rc1 == 0 && rc2 < 0, 2);
+
+	close(s1); close(s2);
+	cleanup_test();
+}
+
+/*
+ * Use bind(2) on two AF_STP sockets using same port and "eth0" interface and
+ * "all".
+ * Call should fail.
+ */
+
+static void test_two_binds_same_if_eth0_all(void)
+{
+	int s1, s2;
+	int rc1, rc2;
+	struct sockaddr_stp sas1, sas2;
+	const unsigned short port = 12345;
+
+	init_test();
+
+	s1 = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas1.sas_family = AF_STP;
+	sas1.sas_port = htons(port);
+	sas1.sas_ifindex = if_nametoindex("eth0");
+	rc1 = bind(s1, (struct sockaddr *) &sas1, sizeof(struct sockaddr_stp));
+
+	s2 = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas2.sas_family = AF_STP;
+	sas2.sas_port = htons(port);
+	sas2.sas_ifindex = 0;
+	rc2 = bind(s2, (struct sockaddr *) &sas2, sizeof(struct sockaddr_stp));
+
+	test(__FUNCTION__, rc1 == 0 && rc2 < 0, 2);
+
+	close(s1); close(s2);
+	cleanup_test();
+}
+
+static ssize_t sendto_message(int sockfd, struct sockaddr_stp *sas,
+	char *buf, size_t len)
+{
+	return sendto(sockfd, buf, len, 0, (SSA *) sas, sizeof(*sas));
+}
+
+static ssize_t sendmsg_message(int sockfd, struct sockaddr_stp *sas,
+	char *buf, size_t len)
+{
+	struct iovec iov;
+	struct msghdr msg;
+
+	iov.iov_base = buf;
+	iov.iov_len = len;
+	msg.msg_name = sas;
+	msg.msg_namelen = sizeof(*sas);
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+	msg.msg_control = NULL;
+	msg.msg_controllen = 0;
+	msg.msg_flags = 0;
+
+	return sendmsg(sockfd, &msg, 0);
+}
+
+static ssize_t send_message(int sockfd, char *buf, size_t len)
+{
+	return send(sockfd, buf, len, 0);
+}
+
+/*
+ * Use recvfrom(2) to receive message. We don't care what is the source
+ * address of the message.
+ */
+
+static ssize_t recvfrom_message(int sockfd, char *buf, size_t len)
+{
+	dprintf("ready to receive using recvfrom\n");
+	return recvfrom(sockfd, buf, len, 0, NULL, NULL);
+}
+
+/*
+ * Use recvmsg(2) to receive message. We don't care what is the source
+ * address of the message.
+ */
+
+static ssize_t recvmsg_message(int sockfd, char *buf, size_t len)
+{
+	struct iovec iov;
+	struct msghdr msg;
+
+	iov.iov_base = buf;
+	iov.iov_len = len;
+	msg.msg_name = NULL;
+	msg.msg_namelen = 0;
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+	msg.msg_control = NULL;
+	msg.msg_controllen = 0;
+	msg.msg_flags = 0;
+
+	return recvmsg(sockfd, &msg, 0);
+}
+
+/*
+ * Can not use recv(2) on datagram sockets. call recvfrom_message().
+ */
+
+static ssize_t recv_message(int sockfd, char *buf, size_t len)
+{
+	dprintf("ready to receive using recv\n");
+	return recv(sockfd, buf, len, 0);
+}
+
+/*
+ * Use sendto(2) on a socket.
+ */
+
+static void test_sendto(void)
+{
+	int s;
+	int rc;
+	struct sockaddr_stp sas, remote_sas;
+	const unsigned short port = 12345, remote_port = 54321;
+	char bufout[BUFLEN] = DEFAULT_SENDER_MESSAGE;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas.sas_family = AF_STP;
+	sas.sas_port = htons(port);
+	sas.sas_ifindex = if_nametoindex("lo");
+	bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp));
+
+	remote_sas.sas_family = AF_STP;
+	remote_sas.sas_port = htons(remote_port);
+	remote_sas.sas_ifindex = 0;
+	memcpy(remote_sas.sas_addr, ether_aton("00:00:00:00:00:00"),
+		sizeof(remote_sas.sas_addr));
+
+	rc = sendto_message(s, &remote_sas, bufout, BUFLEN);
+
+	test(__FUNCTION__, rc >= 0, 5);
+
+	close(s);
+	cleanup_test();
+}
+
+/*
+ * Use sendmsg(2) on a socket.
+ */
+
+static void test_sendmsg(void)
+{
+	int s;
+	int rc;
+	struct sockaddr_stp sas, remote_sas;
+	const unsigned short port = 12345, remote_port = 54321;
+	char bufout[BUFLEN] = DEFAULT_SENDER_MESSAGE;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas.sas_family = AF_STP;
+	sas.sas_port = htons(port);
+	sas.sas_ifindex = if_nametoindex("lo");
+	bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp));
+
+	remote_sas.sas_family = AF_STP;
+	remote_sas.sas_port = htons(remote_port);
+	remote_sas.sas_ifindex = 0;
+	memcpy(remote_sas.sas_addr, ether_aton("00:00:00:00:00:00"),
+		sizeof(remote_sas.sas_addr));
+
+	rc = sendmsg_message(s, &remote_sas, bufout, BUFLEN);
+
+	test(__FUNCTION__, rc >= 0, 3);
+
+	close(s);
+	cleanup_test();
+}
+
+/*
+ * Connect local socket to remote AF_STP socket.
+ */
+
+static void test_connect(void)
+{
+	int s;
+	int rc;
+	struct sockaddr_stp sas, remote_sas;
+	const unsigned short port = 12345, remote_port = 54321;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas.sas_family = AF_STP;
+	sas.sas_port = htons(port);
+	sas.sas_ifindex = if_nametoindex("lo");
+	bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp));
+
+	remote_sas.sas_family = AF_STP;
+	remote_sas.sas_port = htons(remote_port);
+	remote_sas.sas_ifindex = 0;
+	memcpy(remote_sas.sas_addr, ether_aton("00:00:00:00:00:00"),
+		sizeof(remote_sas.sas_addr));
+
+	rc = connect(s, (struct sockaddr *) &remote_sas, sizeof(remote_sas));
+
+	test(__FUNCTION__, rc >= 0, 5);
+
+	close(s);
+	cleanup_test();
+}
+
+/*
+ * Use send(2) on a connected socket.
+ */
+
+static void test_send(void)
+{
+	int s;
+	int rc;
+	struct sockaddr_stp sas, remote_sas;
+	const unsigned short port = 12345, remote_port = 54321;
+	char bufout[BUFLEN] = DEFAULT_SENDER_MESSAGE;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas.sas_family = AF_STP;
+	sas.sas_port = htons(port);
+	sas.sas_ifindex = if_nametoindex("lo");
+	bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp));
+
+	remote_sas.sas_family = AF_STP;
+	remote_sas.sas_port = htons(remote_port);
+	remote_sas.sas_ifindex = 0;
+	memcpy(remote_sas.sas_addr, ether_aton("00:00:00:00:00:00"),
+		sizeof(remote_sas.sas_addr));
+
+	rc = connect(s, (SSA *) &remote_sas, sizeof(remote_sas));
+	assert(rc == 0);
+
+	rc = send_message(s, bufout, BUFLEN);
+
+	test(__FUNCTION__, rc >= 0, 5);
+
+	close(s);
+	cleanup_test();
+}
+
+/*
+ * Read values from STP_PROC_FULL_FILENAME.
+ */
+
+static int stp_proc_read_values(void)
+{
+	char buffer[256];
+	FILE *f;
+
+	f = fopen(STP_PROC_FULL_FILENAME, "rt");
+	if (f == NULL)
+		return -1;
+
+	/* read column line */
+	fgets(buffer, 256, f);
+
+	/* read values line */
+	fscanf(f, "%d %d %d %d %d %d",
+		&rx_pkts, &hdr_err, &csum_err, &no_sock, &no_buffs, &tx_pkts);
+	dprintf("read: %d %d %d %d %d %d\n",
+		rx_pkts, hdr_err, csum_err, no_sock, no_buffs, tx_pkts);
+
+	fclose(f);
+
+	return 0;
+}
+
+/*
+ * Send packet updates RxPkts column in STP_PROC_FULL_FILENAME.
+ * Expected values are 1, 1.
+ */
+
+static void test_stat_tx(void)
+{
+	int s;
+	int rc;
+	struct sockaddr_stp sas, remote_sas;
+	const unsigned short port = 12345, remote_port = 54321;
+	char bufout[BUFLEN] = DEFAULT_SENDER_MESSAGE;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas.sas_family = AF_STP;
+	sas.sas_port = htons(port);
+	sas.sas_ifindex = if_nametoindex("lo");
+	bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp));
+
+	remote_sas.sas_family = AF_STP;
+	remote_sas.sas_port = htons(remote_port);
+	remote_sas.sas_ifindex = 0;
+	memcpy(remote_sas.sas_addr, ether_aton("00:00:00:00:00:00"),
+		sizeof(remote_sas.sas_addr));
+
+	rc = connect(s, (SSA *) &remote_sas, sizeof(remote_sas));
+	assert(rc == 0);
+
+	send_message(s, bufout, BUFLEN);
+
+	close(s);
+
+	stp_proc_read_values();
+
+	test(__FUNCTION__, tx_pkts == 1, 3);
+
+	cleanup_test();
+}
+
+/*
+ * Start sender process.
+ *
+ * action switches between sendto(2), sendmsg(2), send(2) and whether
+ * to do ping_pong or not.
+ */
+
+static pid_t start_sender(enum socket_action action)
+{
+	pid_t pid;
+	int s;
+	struct sockaddr_stp sas, remote_sas;
+	const unsigned short port = 12345, remote_port = 54321;
+	char bufin[BUFLEN];
+	char bufout[BUFLEN] = DEFAULT_SENDER_MESSAGE;
+	ssize_t bytes_recv = 0, bytes_sent = 0;
+	sem_t *sem;
+
+	/* set bufin to 0 for testing purposes (it should be overwritten) */
+	memset(bufin, 0, BUFLEN);
+
+	pid = fork();
+	DIE(pid < 0, "fork");
+
+	switch (pid) {
+	case 0:		/* child process */
+		break;
+
+	default:	/* parent process */
+		return pid;
+	}
+
+	/* only child process (sender) is running */
+
+	sem = sem_open(SEM_NAME_SENDER, 0);
+	if (sem == SEM_FAILED)
+		exit(EXIT_FAILURE);
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas.sas_family = AF_STP;
+	sas.sas_port = htons(port);
+	sas.sas_ifindex = if_nametoindex("lo");
+	bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp));
+
+	remote_sas.sas_family = AF_STP;
+	remote_sas.sas_port = htons(remote_port);
+	remote_sas.sas_ifindex = 0;
+	memcpy(remote_sas.sas_addr, ether_aton("00:00:00:00:00:00"),
+		sizeof(remote_sas.sas_addr));
+	if (action == ACTION_SEND || action == ACTION_SEND_PING_PONG) {
+		int rc;
+
+		rc = connect(s, (SSA *) &remote_sas, sizeof(remote_sas));
+		assert(rc == 0);
+	}
+
+	switch (action) {
+	case ACTION_SENDTO:
+	case ACTION_SENDTO_PING_PONG:
+		bytes_sent = sendto_message(s, &remote_sas, bufout, BUFLEN);
+		dprintf("sent %s\n", bufout);
+		break;
+
+	case ACTION_SENDMSG:
+	case ACTION_SENDMSG_PING_PONG:
+		bytes_sent = sendmsg_message(s, &remote_sas, bufout, BUFLEN);
+		dprintf("sent %s\n", bufout);
+		break;
+
+	case ACTION_SEND:
+	case ACTION_SEND_PING_PONG:
+		bytes_sent = send_message(s, bufout, BUFLEN);
+		dprintf("sent %s\n", bufout);
+		break;
+
+	default:
+		break;
+	}
+
+	switch (action) {
+	case ACTION_SENDTO_PING_PONG:
+		bytes_recv = recvfrom_message(s, bufin, BUFLEN);
+		dprintf("received %s\n", bufin);
+		break;
+	case ACTION_SENDMSG_PING_PONG:
+		bytes_recv = recvmsg_message(s, bufin, BUFLEN);
+		dprintf("received %s\n", bufin);
+		break;
+	case ACTION_SEND_PING_PONG:
+		bytes_recv = recv_message(s, bufin, BUFLEN);
+		dprintf("received %s\n", bufin);
+		break;
+	default:
+		break;
+	}
+
+	/* Let the parent know we're done. */
+	sem_post(sem);
+
+	/* exit with EXIT_SUCCESS in case of successful communication */
+	switch (action) {
+	case ACTION_SENDTO:
+	case ACTION_SEND:
+	case ACTION_SENDMSG:
+		if (bytes_sent > 0)
+			exit(EXIT_SUCCESS);
+		break;
+
+	case ACTION_SENDMSG_PING_PONG:
+	case ACTION_SENDTO_PING_PONG:
+	case ACTION_SEND_PING_PONG:
+		dprintf("(ping_pong) bytes_sent: %d, bytes_recv: %d, strcmp: %d\n",
+			bytes_sent, bytes_recv, strcmp(bufin, bufout));
+		dprintf("bufin: #%s#, bufout: #%s#\n", bufin, bufout);
+		if (bytes_sent > 0 && bytes_recv > 0 &&
+			strcmp(bufin, DEFAULT_RECEIVER_MESSAGE) == 0)
+			exit(EXIT_SUCCESS);
+		break;
+	}
+
+	exit(EXIT_FAILURE);
+
+	/* is not reached */
+	return 0;
+}
+
+/*
+ * Start receiver process.
+ *
+ * action switches between sendto(2), sendmsg(2), send(2) and whether
+ * to do ping_pong or not.
+ */
+
+static pid_t start_receiver(enum socket_action action)
+{
+	pid_t pid;
+	int s;
+	struct sockaddr_stp sas, remote_sas;
+	const unsigned short port = 54321, remote_port = 12345;
+	char bufin[BUFLEN];
+	char bufout[BUFLEN] = DEFAULT_RECEIVER_MESSAGE;
+	ssize_t bytes_recv = 0, bytes_sent = 0;
+	sem_t *sem;
+
+	/* set bufin to 0 for testing purposes (it should be overwritten) */
+	memset(bufin, 0, BUFLEN);
+
+	pid = fork();
+	DIE(pid < 0, "fork");
+
+	switch (pid) {
+	case 0:		/* child process */
+		break;
+
+	default:	/* parent process */
+		return pid;
+	}
+
+	/* only child process (receiver) is running */
+
+	sem = sem_open(SEM_NAME_RECEIVER, 0);
+	if (sem == SEM_FAILED)
+		exit(EXIT_FAILURE);
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas.sas_family = AF_STP;
+	sas.sas_port = htons(port);
+	sas.sas_ifindex = if_nametoindex("lo");
+	bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp));
+
+	remote_sas.sas_family = AF_STP;
+	remote_sas.sas_port = htons(remote_port);
+	remote_sas.sas_ifindex = 0;
+	memcpy(remote_sas.sas_addr, ether_aton("00:00:00:00:00:00"),
+		sizeof(remote_sas.sas_addr));
+
+	if (action == ACTION_SEND || action == ACTION_SEND_PING_PONG) {
+		int rc;
+
+		rc = connect(s, (SSA *) &remote_sas, sizeof(remote_sas));
+		assert(rc == 0);
+		dprintf("connected\n");
+	}
+
+	/* We're set up, let the parent know. */
+	sem_post(sem);
+
+	switch (action) {
+	case ACTION_SENDTO:
+	case ACTION_SENDTO_PING_PONG:
+		bytes_recv = recvfrom_message(s, bufin, BUFLEN);
+		dprintf("received %s\n", bufin);
+		break;
+
+	case ACTION_SENDMSG:
+	case ACTION_SENDMSG_PING_PONG:
+		bytes_recv = recvmsg_message(s, bufin, BUFLEN);
+		dprintf("received %s\n", bufin);
+		break;
+
+	case ACTION_SEND:
+	case ACTION_SEND_PING_PONG:
+		bytes_recv = recv_message(s, bufin, BUFLEN);
+		dprintf("received %s\n", bufin);
+		break;
+
+	default:
+		break;
+	}
+
+	switch (action) {
+	case ACTION_SENDTO_PING_PONG:
+		bytes_sent = sendto_message(s, &remote_sas, bufout, BUFLEN);
+		dprintf("sent %s\n", bufout);
+		break;
+	case ACTION_SENDMSG_PING_PONG:
+		bytes_sent = sendmsg_message(s, &remote_sas, bufout, BUFLEN);
+		dprintf("sent %s\n", bufout);
+		break;
+	case ACTION_SEND_PING_PONG:
+		bytes_sent = send_message(s, bufout, BUFLEN);
+		dprintf("sent %s\n", bufout);
+		break;
+	default:
+		break;
+	}
+
+	/* Let the parent know we're done. */
+	sem_post(sem);
+
+	/* exit with EXIT_SUCCESS in case of successful communication */
+	switch (action) {
+	case ACTION_SENDTO:
+	case ACTION_SEND:
+	case ACTION_SENDMSG:
+		if (bytes_recv > 0)
+			exit(EXIT_SUCCESS);
+		break;
+
+	case ACTION_SENDMSG_PING_PONG:
+	case ACTION_SENDTO_PING_PONG:
+	case ACTION_SEND_PING_PONG:
+		dprintf("(ping_pong) bytes_sent: %d, bytes_recv: %d\n",
+				bytes_sent, bytes_recv);
+		dprintf("bufin: #%s#, bufout: #%s#\n", bufin, bufout);
+		if (bytes_recv > 0 && bytes_sent > 0 &&
+			strcmp(bufin, DEFAULT_SENDER_MESSAGE) == 0)
+			exit(EXIT_SUCCESS);
+		break;
+	}
+
+	exit(EXIT_FAILURE);
+
+	/* is not reached */
+	return 0;
+}
+
+int wait_for_semaphore(sem_t *sem, unsigned int secs)
+{
+	struct timespec ts;
+	int ret;
+
+	ret = clock_gettime(CLOCK_REALTIME, &ts);
+	assert(ret == 0);
+
+	ts.tv_sec += secs;
+
+	ret = sem_timedwait(sem, &ts);
+	return ret;
+}
+
+/*
+ * Wrapper call for running a sender and a receiver process.
+ *
+ * action switches between sendto(2), sendmsg(2), send(2) and whether
+ * to do ping_pong or not.
+ *
+ * Returns boolean value: 1 in case of successful run, 0 otherwise.
+ */
+
+static int run_sender_receiver(enum socket_action action)
+{
+	pid_t pid_r = 0, pid_s = 0;
+	int rc1, rc2, ret;
+	int status1, status2;
+	sem_t *sem_r, *sem_s;
+
+	/* Create two named semaphores used to communicate
+	 * with the child processes
+	 */
+	sem_r = sem_open(SEM_NAME_RECEIVER, O_CREAT, (mode_t)0644, 0);
+	assert(sem_r != SEM_FAILED);
+	sem_s = sem_open(SEM_NAME_SENDER, O_CREAT, (mode_t)0644, 0);
+	assert(sem_s != SEM_FAILED);
+
+	/* start the receiver */
+	pid_r = start_receiver(action);
+	assert(pid_r > 0);
+	/* wait for it to bind */
+	wait_for_semaphore(sem_r, RECV_TIMEOUT);
+
+	/* Receiver is set up, start the sender now. */
+	pid_s = start_sender(action);
+	assert(pid_s > 0);
+
+	/* Wait for both to finish. */
+	rc1 = wait_for_semaphore(sem_r, SENDRECV_TIMEOUT);
+	ret = waitpid(pid_r, &status1, rc1 ? WNOHANG : 0);
+	assert(ret >= 0);
+	kill(pid_r, SIGTERM); kill(pid_r, SIGKILL);
+
+	rc2 = wait_for_semaphore(sem_s, SENDRECV_TIMEOUT);
+	ret = waitpid(pid_s, &status2, rc2 ? WNOHANG : 0);
+	assert(ret >= 0);
+	kill(pid_s, SIGTERM); kill(pid_s, SIGKILL);
+
+	sem_close(sem_r); sem_unlink(SEM_NAME_RECEIVER);
+	sem_close(sem_s); sem_unlink(SEM_NAME_SENDER);
+
+	return !rc1 && !rc2 &&
+	       WIFEXITED(status1) && WEXITSTATUS(status1) == EXIT_SUCCESS &&
+	       WIFEXITED(status2) && WEXITSTATUS(status2) == EXIT_SUCCESS;
+}
+
+/*
+ * Send a datagram on one end and receive it on the other end.
+ * Use sendto(2) and recvfrom(2).
+ */
+
+static void test_sendto_recvfrom(void)
+{
+	int rc;
+
+	init_test();
+
+	rc = run_sender_receiver(ACTION_SENDTO);
+
+	test(__FUNCTION__, rc != 0, 10);
+
+	cleanup_test();
+}
+
+/*
+ * Send and receive packet updates RxPkts and TxPkts columns in
+ * STP_PROC_FULL_FILENAME. Expected values are 1, 1.
+ */
+
+static void test_stat_tx_rx(void)
+{
+	init_test();
+
+	run_sender_receiver(ACTION_SENDTO);
+
+	stp_proc_read_values();
+
+	test(__FUNCTION__, tx_pkts == 1 && rx_pkts == 1, 3);
+
+	cleanup_test();
+}
+
+/*
+ * Send a packet and then wait for a reply.
+ */
+
+static void test_sendto_recvfrom_ping_pong(void)
+{
+	int rc;
+
+	init_test();
+
+	rc = run_sender_receiver(ACTION_SENDTO_PING_PONG);
+
+	test(__FUNCTION__, rc != 0, 5);
+
+	cleanup_test();
+}
+
+/*
+ * Send and receive ping pong updates RxPkts and TxPkts column in
+ * STP_PROC_FULL_FILENAME. Expected values are 2, 2.
+ */
+
+static void test_stat_tx_rx_ping_pong(void)
+{
+	init_test();
+
+	run_sender_receiver(ACTION_SENDTO_PING_PONG);
+
+	stp_proc_read_values();
+	stp_proc_read_values();
+
+	test(__FUNCTION__, tx_pkts == 2 && rx_pkts == 2, 3);
+
+	cleanup_test();
+}
+
+/*
+ * Send a datagram on one end and receive it on the other end.
+ * Use sendmsg(2) and recvmsg(2).
+ */
+
+static void test_sendmsg_recvmsg(void)
+{
+	int rc;
+
+	init_test();
+
+	rc = run_sender_receiver(ACTION_SENDMSG);
+
+	test(__FUNCTION__, rc != 0, 5);
+
+	cleanup_test();
+}
+
+/*
+ * Send a packet and then wait for a reply.
+ */
+
+static void test_sendmsg_recvmsg_ping_pong(void)
+{
+	int rc;
+
+	init_test();
+
+	rc = run_sender_receiver(ACTION_SENDMSG_PING_PONG);
+
+	test(__FUNCTION__, rc != 0, 3);
+
+	cleanup_test();
+}
+
+/*
+ * Send a packet on one end and receive it on the other end.
+ * Use send(2) and recv(2).
+ */
+
+static void test_send_receive(void)
+{
+	int rc;
+
+	init_test();
+
+	rc = run_sender_receiver(ACTION_SEND);
+
+	test(__FUNCTION__, rc != 0, 5);
+
+	cleanup_test();
+}
+
+/*
+ * Send a packet and then wait for a reply.
+ */
+
+static void test_send_receive_ping_pong(void)
+{
+	int rc;
+
+	init_test();
+
+	rc = run_sender_receiver(ACTION_SEND_PING_PONG);
+
+	test(__FUNCTION__, rc != 0, 3);
+
+	cleanup_test();
+}
+
+static void (*test_fun_array[])(void) = {
+	NULL,
+	test_insmod_rmmod,
+	test_proto_name_exists_after_insmod,
+	test_proto_name_inexistent_after_rmmod,
+	test_proc_entry_exists_after_insmod,
+	test_proc_entry_inexistent_after_rmmod,
+	test_socket,
+	test_two_sockets,
+	test_socket_bad_socket_type,
+	test_socket_bad_protocol,
+	test_close,
+	test_close_closed_socket,
+	test_bind,
+	test_bind_eth0,
+	test_two_binds,
+	test_bind_bad_address,
+	test_two_binds_same_if,
+	test_two_binds_same_if_eth0,
+	test_two_binds_same_if_all_eth0,
+	test_two_binds_same_if_eth0_all,
+	test_sendto,
+	test_sendmsg,
+	test_connect,
+	test_send,
+	test_stat_tx,
+	test_sendto_recvfrom,
+	test_stat_tx_rx,
+	test_sendto_recvfrom_ping_pong,
+	test_stat_tx_rx_ping_pong,
+	test_sendmsg_recvmsg,
+	test_sendmsg_recvmsg_ping_pong,
+	test_send_receive,
+	test_send_receive_ping_pong,
+};
+
+/*
+ * Usage message for invalid executable call.
+ */
+
+static void usage(const char *argv0)
+{
+	fprintf(stderr, "Usage: %s test_no\n\n", argv0);
+	exit(EXIT_FAILURE);
+}
+
+int main(int argc, char **argv)
+{
+	int test_idx;
+
+	if (argc != 2)
+		usage(argv[0]);
+
+	test_idx = atoi(argv[1]);
+
+	if (test_idx < 1 ||
+		test_idx >= sizeof(test_fun_array)/sizeof(test_fun_array[0])) {
+		fprintf(stderr, "Error: test index %d is out of bounds\n",
+			test_idx);
+		exit(EXIT_FAILURE);
+	}
+
+	srand(time(NULL));
+	srand48(time(NULL));
+	test_fun_array[test_idx]();
+
+	return 0;
+}
diff --git a/checker/4-stp-checker/_test/stp_test.h b/checker/4-stp-checker/_test/stp_test.h
new file mode 100644
index 0000000000000000000000000000000000000000..fb708433c0269f38454ccb3d6903e2c8c410468a
--- /dev/null
+++ b/checker/4-stp-checker/_test/stp_test.h
@@ -0,0 +1,31 @@
+/*
+ * SO2 Transport Protocol - test suite specific header
+ */
+
+#ifndef STP_TEST_H_
+#define STP_TEST_H_		1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* STP test suite macros and structures */
+#define MODULE_NAME		"af_stp"
+#define MODULE_FILENAME		MODULE_NAME ".ko"
+
+#define SEM_NAME_RECEIVER	"/receiver_sem"
+#define SEM_NAME_SENDER		"/sender_sem"
+
+/* timeouts waiting for receiver/sender */
+#define RECV_TIMEOUT			1
+#define SENDRECV_TIMEOUT		3
+
+/* messages used for "ping-pong" between sender and receiver */
+#define DEFAULT_SENDER_MESSAGE		"You called down the thunder"
+#define DEFAULT_RECEIVER_MESSAGE	"now reap the whirlwind"
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/checker/4-stp-checker/_test/test.h b/checker/4-stp-checker/_test/test.h
new file mode 100644
index 0000000000000000000000000000000000000000..4bcafad9c7d0f5b241a0241071301954c3bf9601
--- /dev/null
+++ b/checker/4-stp-checker/_test/test.h
@@ -0,0 +1,63 @@
+/*
+ * generic test suite
+ *
+ * test macros and headers
+ */
+
+#ifndef TEST_H_
+#define TEST_H_		1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+
+/* to be defined by calling program */
+extern int max_points;
+
+/*
+ * uncommend EXIT_IF_FAIL macro in order to stop test execution
+ * at first failed test
+ */
+
+/*#define EXIT_IF_FAIL	1*/
+
+#if defined(EXIT_IF_FAIL)
+#define test_do_fail(points)		\
+	do {				\
+		printf("failed\n");	\
+		exit(EXIT_FAILURE);	\
+	} while (0)
+#else
+#define test_do_fail(points)		\
+	printf("failed  [  0/%3d]\n", max_points)
+#endif
+
+#define test_do_pass(points)		\
+	printf("passed  [%3d/%3d]\n", points, max_points)
+
+#define test(message, test, points)				\
+	do {							\
+		size_t i;					\
+		int t = (test);					\
+								\
+		printf("%s", message);				\
+		fflush(stdout);					\
+								\
+		for (i = 0; i < 60 - strlen(message); i++)	\
+			putchar('.');				\
+								\
+		if (!t)						\
+			test_do_fail(points);			\
+		else						\
+			test_do_pass(points);			\
+								\
+		fflush(stdout);					\
+	} while (0)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/checker/4-stp-checker/_test/util.h b/checker/4-stp-checker/_test/util.h
new file mode 100644
index 0000000000000000000000000000000000000000..f06cb833b99635e27c494498fd6b981e50564dbc
--- /dev/null
+++ b/checker/4-stp-checker/_test/util.h
@@ -0,0 +1,69 @@
+/*
+ * useful structures/macros
+ */
+
+#ifndef UTIL_H_
+#define UTIL_H_		1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#if defined(_WIN32)
+
+#include <windows.h>
+
+static VOID PrintLastError(const PCHAR message)
+{
+	CHAR errBuff[1024];
+
+	FormatMessage(
+		FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_MAX_WIDTH_MASK,
+		NULL,
+		GetLastError(),
+		0,
+		errBuff,
+		sizeof(errBuff) - 1,
+		NULL);
+
+	fprintf(stderr, "%s: %s\n", message, errBuff);
+}
+
+#define ERR(call_description)					\
+	do {							\
+		fprintf(stderr, "(%s, %d): ",			\
+				__FILE__, __LINE__);		\
+			PrintLastError(call_description);	\
+	} while (0)
+
+#elif defined(__linux__)
+
+/* error printing macro */
+#define ERR(call_description)				\
+	do {						\
+		fprintf(stderr, "(%s, %d): ",		\
+				__FILE__, __LINE__);	\
+			perror(call_description);	\
+	} while (0)
+
+#else
+  #error "Unknown platform"
+#endif
+
+/* print error (call ERR) and exit */
+#define DIE(assertion, call_description)		\
+	do {						\
+		if (assertion) {			\
+			ERR(call_description);		\
+			exit(EXIT_FAILURE);		\
+		}					\
+	} while (0)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/checker/checker.sh b/checker/checker.sh
index 3e1d1a630cbeddab6f15eedbd5010c17a2ede37e..424e3dcd7ae3e5e8fbacb3d71dc7b0f1cfa5f932 100755
--- a/checker/checker.sh
+++ b/checker/checker.sh
@@ -1,34 +1,104 @@
 #!/bin/bash
 
-TIMEOUT=300 # 5 min
 SO2_WORKSPACE=/linux/tools/labs
+SO2_VM_LOG=/tmp/so2_vm_log.txt
 
+
+ASSIGNMENT0_TIMEOUT=300 # 5 min
 ASSIGNMENT0_MOD=list.ko
 ASSIGNMENT0_DIR=${SO2_WORKSPACE}/skels/assignments/0-list
+ASSIGNMENT0_CHECKER_LOCAL_DIR=checker/0-list-checker
 ASSIGNMENT0_CHECKER_DIR=${SO2_WORKSPACE}/skels/assignments/0-list-checker
 ASSIGNMENT0_OUTPUT=${SO2_WORKSPACE}/skels/0-list-output
 ASSIGNMENT0_FINISHED=${SO2_WORKSPACE}/skels/0-list-finished
 
+ASSIGNMENT1_TIMEOUT=300 # 5 min
 ASSIGNMENT1_MOD=tracer.ko
 ASSIGNMENT1_DIR=${SO2_WORKSPACE}/skels/assignments/1-tracer
+ASSIGNMENT1_CHECKER_LOCAL_DIR=checker/1-tracer-checker
 ASSIGNMENT1_CHECKER_DIR=${SO2_WORKSPACE}/skels/assignments/1-tracer-checker
 ASSIGNMENT1_OUTPUT=${SO2_WORKSPACE}/skels/1-tracer-output
 ASSIGNMENT1_FINISHED=${SO2_WORKSPACE}/skels/1-tracer-finished
 ASSIGNMENT1_HEADER_OVERWRITE=${SO2_WORKSPACE}/templates/assignments/1-tracer/tracer.h
 ASSIGNMENT1_CHECKER_AUX_LIST="${ASSIGNMENT1_CHECKER_DIR}/_helper/tracer_helper.ko"
 
+ASSIGNMENT2_TIMEOUT=300 # 5 min
+ASSIGNMENT2_MOD=uart16550.ko
+ASSIGNMENT2_DIR=${SO2_WORKSPACE}/skels/assignments/2-uart
+ASSIGNMENT2_CHECKER_LOCAL_DIR=checker/2-uart-checker
+ASSIGNMENT2_CHECKER_DIR=${SO2_WORKSPACE}/skels/assignments/2-uart-checker
+ASSIGNMENT2_OUTPUT=${SO2_WORKSPACE}/skels/2-uart-output
+ASSIGNMENT2_FINISHED=${SO2_WORKSPACE}/skels/2-uart-finished
+ASSIGNMENT2_HEADER_OVERWRITE=${SO2_WORKSPACE}/templates/assignments/2-uart/uart16550.h
+ASSIGNMENT2_CHECKER_AUX_LIST="${ASSIGNMENT2_CHECKER_DIR}/_test/solution.ko"
+
+ASSIGNMENT3_TIMEOUT=360 # 6 min
+ASSIGNMENT3_MOD=ssr.ko
+ASSIGNMENT3_DIR=${SO2_WORKSPACE}/skels/assignments/3-raid
+ASSIGNMENT3_CHECKER_LOCAL_DIR=checker/3-raid-checker
+ASSIGNMENT3_CHECKER_DIR=${SO2_WORKSPACE}/skels/assignments/3-raid-checker
+ASSIGNMENT3_OUTPUT=${SO2_WORKSPACE}/skels/3-raid-output
+ASSIGNMENT3_FINISHED=${SO2_WORKSPACE}/skels/3-raid-finished
+ASSIGNMENT3_HEADER_OVERWRITE=${SO2_WORKSPACE}/templates/assignments/3-raid/ssr.h
+ASSIGNMENT3_CHECKER_AUX_LIST="${ASSIGNMENT3_CHECKER_DIR}/_test/run-test"
+
+ASSIGNMENT4_TIMEOUT=300 # 5 min
+ASSIGNMENT4_MOD=af_stp.ko
+ASSIGNMENT4_DIR=${SO2_WORKSPACE}/skels/assignments/4-stp
+ASSIGNMENT4_CHECKER_LOCAL_DIR=checker/4-stp-checker
+ASSIGNMENT4_CHECKER_DIR=${SO2_WORKSPACE}/skels/assignments/4-stp-checker
+ASSIGNMENT4_OUTPUT=${SO2_WORKSPACE}/skels/4-stp-output
+ASSIGNMENT4_FINISHED=${SO2_WORKSPACE}/skels/4-stp-finished
+ASSIGNMENT4_HEADER_OVERWRITE=${SO2_WORKSPACE}/templates/assignments/4-stp/stp.h
+#ASSIGNMENT4_CHECKER_AUX_LIST="${ASSIGNMENT3_CHECKER_DIR}/_test/run-test"
+
+
 usage()
 {
 	echo "Usage: $0 <assignment>"
 	exit 1
 }
 
+
+
+recover_grade_from_timeout()
+{
+	local output=$1
+	if [ ! -f $output ]; then
+		echo "$output not available"
+	else
+		points_total=$(echo $(cat $output | grep "....passed" | egrep -o "/.*[0-9]+\.*[0-9]*.*\]" | egrep -o "[0-9]+\.*[0-9]*" | head -n 1))
+		list=$(echo $(cat $output | grep "....passed" | egrep  -o "\[.*[0-9]+\.*[0-9]*.*\/" |  egrep -o "[0-9]+\.*[0-9]*") | sed -e 's/\s\+/,/g')
+		recovered_points=$(python3 -c "print(sum([$list]))")
+		echo "Recovered from timeout => Total: [$recovered_points/$points_total]"
+		echo "Please note that this is not a DIRECT checker output! Other penalties may be applied!"
+		echo "Please contact a teaching assistant"
+		python3 -c "print('Total: ' + str(int ($recovered_points * 100 / $points_total)) + '/' + '100')"
+	fi
+}
+
 timeout_exceeded()
 {
-	echo TIMEOUT EXCEEDED !!! killing the process
-	echo "<VMCK_NEXT_END>"
+	local output=$1
 	pkill -SIGKILL qemu
-	exit 1
+	echo ""
+	echo "TIMEOUT EXCEEDED !!! killing the process"
+	if [[ $RECOVER_GRADE_TIMEOUT == 0 ]]; then
+		if [ -f $output ]; then
+			echo "$output not available"
+		else
+			cat $output
+		fi
+		echo "dumping SO2_VM_LOG=${SO2_VM_LOG} output"
+		cat $SO2_VM_LOG
+
+		echo "The Recover Grade Timeout option is not set! Please contact a teaching assistant!"
+	else
+		recover_grade_from_timeout $output
+	fi
+	echo "<VMCK_NEXT_END>"
+	# exit successfully for vmchecker-next to process output
+        exit 0 # TODO: fixme
 }
 
 compute_total()
@@ -46,8 +116,10 @@ compute_total()
 dump_output()
 {
 	local output=$1
+	local timeout=$2
 	echo "<VMCK_NEXT_BEGIN>"
 	cat $output
+	echo "Running time $timeout/$TIMEOUT"
 
 }
 
@@ -67,18 +139,22 @@ run_checker()
 {
 	local assignment_mod=$1
 	local assignment_dir=$2
-	local checker_dir=$3
-	local output=$4
-	local finished=$5
-	local assignment=$6
-	local header_overwrite=$7
-	local aux_modules=$8
+	local local_checker_dir=$3
+	local checker_dir=$4
+	local output=$5
+	local finished=$6
+	local assignment=$7
+	local header_overwrite=$8
+	local aux_modules=$9
 
 	local module_path="${assignment_dir}/${assignment_mod}"
 
 	echo "Copying the contents of src/ into $assignment_dir"
 	cp src/* $assignment_dir
 
+	echo "Copying the contents of $local_checker_dir into $checker_dir"
+	cp -r $local_checker_dir/* $checker_dir
+
 	echo "Checking if $assignment_mod exists before build"
 	if [ -f $module_path ]; then
 			echo "$assignment_mod shouldn't exists. Removing ${module_path}"
@@ -117,7 +193,7 @@ run_checker()
 		if [ ! -f $module_path ]; then
 			error_message $assignment_mod
 			# exit successfully for vmchecker-next to process output
-			exit 0 # TODO: changeme 
+			exit 0 # TODO: fixme
 		fi
 	
 		# copy *.ko in checker
@@ -133,8 +209,9 @@ run_checker()
 			done
 		fi
 
-		LINUX_ADD_CMDLINE="so2=$assignment" ./qemu/run-qemu.sh &> /dev/null &
-		
+		LINUX_ADD_CMDLINE="so2=$assignment" make checker &> ${SO2_VM_LOG} &
+
+		timeout=0
 		echo -n "CHECKER IS RUNNING"
 		while [ ! -f $finished ]
 		do
@@ -144,27 +221,45 @@ run_checker()
 					dump_output $output
 					compute_total $output
 				fi
-				timeout_exceeded
+				timeout_exceeded $output
 			fi
 			sleep 2
 			(( timeout += 2 ))
 			echo -n .
 		done
 		echo ""
-		dump_output $output
+		dump_output $output $timeout
 		compute_total $output
 	popd &> /dev/null
 }
 
 case $1 in
 	0-list)
-		run_checker $ASSIGNMENT0_MOD $ASSIGNMENT0_DIR $ASSIGNMENT0_CHECKER_DIR $ASSIGNMENT0_OUTPUT $ASSIGNMENT0_FINISHED $1
+		TIMEOUT=$ASSIGNMENT0_TIMEOUT
+		RECOVER_GRADE_TIMEOUT=0 # If set to 1, in case of a timeout, will calculate the total grade based on the output directory
+		run_checker $ASSIGNMENT0_MOD $ASSIGNMENT0_DIR $ASSIGNMENT0_CHECKER_LOCAL_DIR $ASSIGNMENT0_CHECKER_DIR $ASSIGNMENT0_OUTPUT $ASSIGNMENT0_FINISHED $1
 		;;
 	1-tracer)
-		run_checker $ASSIGNMENT1_MOD $ASSIGNMENT1_DIR $ASSIGNMENT1_CHECKER_DIR $ASSIGNMENT1_OUTPUT $ASSIGNMENT1_FINISHED $1 $ASSIGNMENT1_HEADER_OVERWRITE $ASSIGNMENT1_CHECKER_AUX_LIST
-
-
+		TIMEOUT=$ASSIGNMENT1_TIMEOUT
+		RECOVER_GRADE_TIMEOUT=0 # If set to 1, in case of a timeout, will calculate the total grade based on the output directory
+		run_checker $ASSIGNMENT1_MOD $ASSIGNMENT1_DIR $ASSIGNMENT1_CHECKER_LOCAL_DIR $ASSIGNMENT1_CHECKER_DIR $ASSIGNMENT1_OUTPUT $ASSIGNMENT1_FINISHED $1 $ASSIGNMENT1_HEADER_OVERWRITE $ASSIGNMENT1_CHECKER_AUX_LIST
 		;;
+	2-uart)
+		TIMEOUT=$ASSIGNMENT2_TIMEOUT
+		RECOVER_GRADE_TIMEOUT=1 # If set to 1, in case of a timeout, will calculate the total grade based on the output directory
+		run_checker $ASSIGNMENT2_MOD $ASSIGNMENT2_DIR $ASSIGNMENT2_CHECKER_LOCAL_DIR $ASSIGNMENT2_CHECKER_DIR $ASSIGNMENT2_OUTPUT $ASSIGNMENT2_FINISHED $1 $ASSIGNMENT2_HEADER_OVERWRITE $ASSIGNMENT2_CHECKER_AUX_LIST
+ 		;;
+	3-raid)
+		TIMEOUT=$ASSIGNMENT3_TIMEOUT
+		RECOVER_GRADE_TIMEOUT=0 # If set to 1, in case of a timeout, will calculate the total grade based on the output directory
+		run_checker $ASSIGNMENT3_MOD $ASSIGNMENT3_DIR $ASSIGNMENT3_CHECKER_LOCAL_DIR $ASSIGNMENT3_CHECKER_DIR $ASSIGNMENT3_OUTPUT $ASSIGNMENT3_FINISHED $1 $ASSIGNMENT3_HEADER_OVERWRITE $ASSIGNMENT3_CHECKER_AUX_LIST
+		;;
+	4-stp)
+		TIMEOUT=$ASSIGNMENT4_TIMEOUT
+		RECOVER_GRADE_TIMEOUT=0 # If set to 1, in case of a timeout, will calculate the total grade based on the output file
+		run_checker $ASSIGNMENT4_MOD $ASSIGNMENT4_DIR $ASSIGNMENT4_CHECKER_LOCAL_DIR $ASSIGNMENT4_CHECKER_DIR $ASSIGNMENT4_OUTPUT $ASSIGNMENT4_FINISHED $1 $ASSIGNMENT4_HEADER_OVERWRITE
+		;;
+	
 	*)
 		usage
 		;;
diff --git a/checker/checker_daemons/so2_vm_checker_daemon.sh b/checker/checker_daemons/so2_vm_checker_daemon.sh
index afc38b65f3a33a39cbb20e707d6dd1cd1b3790c1..546f236afdea29a8851f21106f3b317387856ee9 100644
--- a/checker/checker_daemons/so2_vm_checker_daemon.sh
+++ b/checker/checker_daemons/so2_vm_checker_daemon.sh
@@ -10,22 +10,58 @@ ASSIGNMENT1_CHECKER=/home/root/skels/assignments/1-tracer-checker
 ASSIGNMENT1_OUTPUT=/home/root/skels/1-tracer-output
 ASSIGNMENT1_FINISHED=/home/root/skels/1-tracer-finished
 
+ASSIGNMENT2_CHECKER=/home/root/skels/assignments/2-uart-checker
+ASSIGNMENT2_OUTPUT=/home/root/skels/2-uart-output
+ASSIGNMENT2_FINISHED=/home/root/skels/2-uart-finished
+
+ASSIGNMENT3_CHECKER=/home/root/skels/assignments/3-raid-checker
+ASSIGNMENT3_OUTPUT=/home/root/skels/3-raid-output
+ASSIGNMENT3_FINISHED=/home/root/skels/3-raid-finished
+
+ASSIGNMENT4_CHECKER=/home/root/skels/assignments/4-stp-checker
+ASSIGNMENT4_OUTPUT=/home/root/skels/4-stp-output
+ASSIGNMENT4_FINISHED=/home/root/skels/4-stp-finished
+
+
 assign0_list()
 {
-        cd $ASSIGNMENT0_CHECKER
-     	   sh _checker &> $ASSIGNMENT0_OUTPUT
-       	   echo FINISHED &> $ASSIGNMENT0_FINISHED
-        cd -
+	cd $ASSIGNMENT0_CHECKER
+		sh _checker &> $ASSIGNMENT0_OUTPUT
+		echo FINISHED &> $ASSIGNMENT0_FINISHED
+	cd -
 }
 
 assign1_tracer()
 {
-        cd $ASSIGNMENT1_CHECKER
-     	   sh _checker &> $ASSIGNMENT1_OUTPUT
-       	   echo FINISHED &> $ASSIGNMENT1_FINISHED
-        cd -
+	cd $ASSIGNMENT1_CHECKER
+		sh _checker &> $ASSIGNMENT1_OUTPUT
+		echo FINISHED &> $ASSIGNMENT1_FINISHED
+	cd -
 }
 
+assign2_uart()
+{
+	cd $ASSIGNMENT2_CHECKER
+		sh _checker &> $ASSIGNMENT2_OUTPUT
+		echo FINISHED &> $ASSIGNMENT2_FINISHED
+	cd -
+}
+
+assign3_raid()
+{
+	cd $ASSIGNMENT3_CHECKER
+		sh _checker &> $ASSIGNMENT3_OUTPUT
+		echo FINISHED &> $ASSIGNMENT3_FINISHED
+	cd -
+}
+
+assign4_stp()
+{
+	cd $ASSIGNMENT4_CHECKER
+		sh _checker &> $ASSIGNMENT4_OUTPUT
+		echo FINISHED &> $ASSIGNMENT4_FINISHED
+	cd -
+}
 
 start()
 {
@@ -37,6 +73,15 @@ start()
 		1-tracer)
 			assign1_tracer
 			;;
+		2-uart)
+			assign2_uart
+			;;
+		3-raid)
+			assign3_raid
+			;;
+		4-stp)
+			assign4_stp
+			;;
                 *)
                         echo "Unknown option"
                         exit 0
diff --git a/local.sh b/local.sh
index 6c0cc4986334fd091ed306122458c9a2123ae44a..adfa8a9d00fb4faa192937edb5b975dc94716c8a 100755
--- a/local.sh
+++ b/local.sh
@@ -153,7 +153,7 @@ docker_push() {
     [ -z "$token" ] && LOG_FATAL "No token provided. Push operation will be aborted..."
 
     LOG_INFO "Pushing Docker image..."
-
+set -x
     docker login "${registry}" -u "${user}" -p "${token}"
     docker push "${registry}/${image_name}:${tag}"
 }
@@ -198,7 +198,7 @@ docker_interactive() {
     set -x
     cp -R ${ASSIGNMENT_CHECKER_DIR}/* "$tmpdir"
     
-    docker run $privileged --rm -it \
+    docker run $privileged --rm -it --cap-add=NET_ADMIN --device /dev/net/tun:/dev/net/tun \
             --mount type=bind,source="$SRC_DIR",target="$ASSIGNMENT_MOUNT_DIR" \
             --mount type=bind,source="$tmpdir",target="$ASSIGNMENT_CHECKER_MOUNT_DIR" \
             --workdir "$SO2_WORKSPACE" \
@@ -265,7 +265,7 @@ checker_main() {
     # In your checker script if you must use absolute paths please use $CI_PROJECT_DIR to reference the location of your directory,
     # otherwise stick to relative paths.
     # It is guaranteed that the current working directory in which checker.sh will run is  $CI_PROJECT_DIR/checker.
-    docker run $privileged --rm \
+    docker run $privileged --rm --cap-add=NET_ADMIN --device /dev/net/tun:/dev/net/tun \
             --mount type=bind,source="$tmpdir",target="$MOUNT_PROJECT_DIRECTORY" \
             "$image_name" /bin/bash -c "rm -rf /usr/local/bin/bash; cd \"$MOUNT_PROJECT_DIRECTORY\"; \"$MOUNT_PROJECT_DIRECTORY/checker/checker.sh\" \"${script_args[@]}\"" # remove bash middleware script