diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..108fa79 --- /dev/null +++ b/.clang-format @@ -0,0 +1,28 @@ +# Стиль форматирования +BasedOnStyle: Google + +# Настройки отступов +IndentWidth: 4 +TabWidth: 4 +UseTab: Never + +# Разрывы строк +ColumnLimit: 100 +BreakBeforeBraces: Allman + +# Пробелы +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false + +# Выравнивание +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: true + +# Указатели и ссылки +PointerAlignment: Left + +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false + +# Сортировка +SortIncludes: true \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..14dd333 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,26 @@ +name: Simple driver tests + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y gcc make clang + + - name: Static analysis + run: clang-tidy prandom.c Galois_Field_256.c test.c -- -I. + + - name: Build driver + run: make + + - name: Build test + run: make test_algo + + - name: Run test + run: make test + + - name: Run with sanitizers + run: make test_sanitized && ./test \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d98624f --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +*.o +*.ko +*.mod.c +*.mod +*.order +*.symvers + Module.symvers +.gfrandom.mod.cmd +gfrandom.mod +compile_commands.json +.cache +*.swp +*.swo +*~ +test +test_gen +expected_gen diff --git a/Galois_Field_256.c b/Galois_Field_256.c new file mode 100644 index 0000000..162c1a8 --- /dev/null +++ b/Galois_Field_256.c @@ -0,0 +1,82 @@ + +// SPDX-License-Identifier: MIT +// Copyright (c) 2026 Denis Potapov + +#include "Galois_Field_256.h" + +#ifdef __KERNEL__ +#include +u16 Module = 0x11B; +u8 ready_to_work = 0; +#else +#include +uint16_t Module = 0x11B; +uint8_t ready_to_work = 0; +#endif + +#define ORDER 256 + +GF256_t alogs[ORDER]; +GF256_t logs[ORDER]; + +void GF256_init(void) { + // порождающий элемент x + 1 + GF256_t elem = 1; // (x + 1)**0 + + for (int i = 0; i < ORDER - 1; i++) { + alogs[i] = elem; // <=> (x + 1)**i = elem + logs[elem] = i; + + uint16_t new_elem = (uint16_t)elem << 1; // <=> new_elem = elem * x + + new_elem ^= elem; // <=> new_elem = elem * x + elem <=> new_elem = elem * (x + 1) + + if (new_elem >= 0x100) { + new_elem ^= Module; + } + elem = (GF256_t)(new_elem); + } + alogs[ORDER - 1] = alogs[0]; + + logs[0] = 0; + + ready_to_work = 1; +} + +GF256_t GF256_Add(GF256_t a, GF256_t b) { + return a ^ b; +} + +GF256_t GF256_Sub(GF256_t a, GF256_t b) { + return a ^ b; +} + +GF256_t GF256_Mul(GF256_t a, GF256_t b) { + if (!ready_to_work) { + GF256_init(); + } + if (a == 0 || b == 0) { + return 0; + } + + uint8_t log_a = logs[a]; + uint8_t log_b = logs[b]; + + uint16_t sum = (log_a + log_b) % (ORDER - 1); + + return alogs[sum]; +} + +GF256_t inverse_mul_element(GF256_t a) { + if (a == 0) { + return 0; + } + if (!ready_to_work) { + GF256_init(); + } + return alogs[255 - logs[a]]; +} + +GF256_t GF256_Div(GF256_t a, GF256_t b) { + return GF256_Mul(a, inverse_mul_element(b)); +} \ No newline at end of file diff --git a/Galois_Field_256.h b/Galois_Field_256.h new file mode 100644 index 0000000..ab4b60a --- /dev/null +++ b/Galois_Field_256.h @@ -0,0 +1,24 @@ + +// SPDX-License-Identifier: MIT +// Copyright (c) 2026 Denis Potapov + +#ifndef Galois_Field_256 +#define Galois_Field_256 + +#ifdef __KERNEL__ +#include +typedef u8 GF256_t; +#else +#include +typedef uint8_t GF256_t; +#endif + +GF256_t GF256_Add(GF256_t a, GF256_t b); + +GF256_t GF256_Sub(GF256_t a, GF256_t b); + +GF256_t GF256_Mul(GF256_t a, GF256_t b); + +GF256_t GF256_Div(GF256_t a, GF256_t b); + +#endif \ No newline at end of file diff --git a/Makefile b/Makefile index 25861e7..ea6600a 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,22 @@ -obj-m += prandom.o - -KDIR := /lib/modules/$(shell uname -r)/build - -PWD := $(shell pwd) - -all: - $(MAKE) -C $(KDIR) M=$(PWD) modules - -clean: - $(MAKE) -C $(KDIR) M=$(PWD) clean \ No newline at end of file +obj-m += gfrandom.o +gfrandom-objs := driver.o prandom.o Galois_Field_256.o + +KDIR := /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +all: + $(MAKE) -C $(KDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KDIR) M=$(PWD) clean + +test_algo: test.c Galois_Field_256.c prandom.c + gcc -o test test.c Galois_Field_256.c prandom.c + +test_sanitized: test.c Galois_Field_256.c prandom.c + gcc -o test test.c Galois_Field_256.c prandom.c -fsanitize=address,undefined -g + +test: test_algo + ./test + +.PHONY: all clean test test diff --git a/README.md b/README.md new file mode 100644 index 0000000..b714f3c --- /dev/null +++ b/README.md @@ -0,0 +1,102 @@ +# prandom + +[![CI](https://github.com/estoniec/prandom/actions/workflows/ci.yml/badge.svg)](https://github.com/estoniec/prandom/actions/workflows/ci.yml) +[![Clang-Tidy](https://img.shields.io/badge/Clang--Tidy-passing-green)](https://clang.llvm.org/docs/ClangFormat.html) + +Linux kernel модуль, предоставляющий генератор псевдослучайных чисел на основе полей Галуа (GF(2⁸)). + +## Описание + +Драйвер создаёт misc-устройство `/dev/gfrandom`, которое возвращает псевдослучайные байты, сгенерированные с использованием арифметики полей Галуа GF(2⁸). Является аналогом `/dev/urandom`. + +### Алгоритм + +Используется регистр сдвига с линейной обратной связью (LFSR) над полем GF(2⁸): +- Состояние: 256 байт (коэффициенты) +- Начальное состояние (seed): t^256 + t^10 + t^5 + t^2 + 1 (или пользовательский) +- Генерируемое значение: сумма произведений ненулевых коэффициентов состояния на соответствующие коэффициенты + +### Используемые компоненты + +- **gfrandom** — драйвер (GPL-3.0) +- **Galois_Field_256** — [библиотека для работы с полями Галуа](https://github.com/DenisPotapov0/Galua-Field-library) (MIT License) + +## Требования + +- Linux kernel headers (для сборки модуля) +- GCC, make +- Для запуска тестов: linux-headers или root доступ для загрузки модуля + +## Детерминированные тестовые данные + +Для удобства пользователей, желающих получить детерминированные предсказуемые числа, в папке `assets` представлены стандартные бинарные файлы по 256 байт каждый: +- `seed_file.bin` — начальное состояние (seed) +- `coeff_file.bin` — коэффициенты + +Эти же файлы используются в тестах №1 и №2. Первые 10 ожидаемых случайных байтов: (HEX: `47 6b 77 79 60 1c bd 3f c9 b2`) — см. `assets/expected1.bin`. + +## Сборка + +### Linux модуль + +```bash +make +``` + +### Userspace тесты + +```bash +make test_algo +./test +``` + +## Использование + +### Загрузка модуля + +```bash +sudo insmod gfrandom.ko +``` + +### Параметры модуля + +- `coeff_file` — путь к файлу с коэффициентами (256 байт) +- `seed_file` — путь к файлу с seed (256 байт) + +Если параметры не указаны, используются автоматически сгенерированные значения. + +Пример использования собственных параметров: +```bash +sudo insmod gfrandom.ko coeff_file=/path/to/coeffs.bin seed_file=/path/to/seed.bin +``` + +### Чтение случайных данных + +```bash +sudo cat /dev/gfrandom | head -c 1024 > /dev/null +``` + +### Выгрузка модуля + +```bash +sudo rmmod gfrandom +``` + +## Тестирование + +Тесты запускаются в CI и локально: + +```bash +make test +``` + +## Лицензия + +- **gfrandom**: GPL-3.0 +- **Galois_Field_256** (Denis Potapov): MIT License + +Подробности см. в [LICENSE](LICENSE). + +## Автор + +Макар Лилль \ No newline at end of file diff --git a/assets/coeff_file.bin b/assets/coeff_file.bin new file mode 100644 index 0000000..3d73e6f --- /dev/null +++ b/assets/coeff_file.bin @@ -0,0 +1,2 @@ +c}zzs/lbڠ~Ioq#7.[/> fcijS#a9 AJ$f6ˌa+l` ]$a{{o2 g~[ebQ0Y2heA}'S }yx2юm +LlFdՕ^d.7ubm@QBo"iR.D<"S \ No newline at end of file diff --git a/assets/expected1.bin b/assets/expected1.bin new file mode 100644 index 0000000..67407da --- /dev/null +++ b/assets/expected1.bin @@ -0,0 +1 @@ +Gkwy`?ɲ \ No newline at end of file diff --git a/assets/seed_file.bin b/assets/seed_file.bin new file mode 100644 index 0000000..f459d91 Binary files /dev/null and b/assets/seed_file.bin differ diff --git a/driver.c b/driver.c new file mode 100644 index 0000000..ea3a6cb --- /dev/null +++ b/driver.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + * prandom – misc-драйвер, предоставляющий случайные данные, аналогично + * /dev/urandom + * + * Copyright (C) 2026 Makar Lill + * + * This driver uses Galois Field arithmetic library written by Denis Potapov + * (lib.c / lib.h). Used with permission. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Galois_Field_256.h" +#include "prandom.h" + +#define DEVICE_NAME "gfrandom" + +MODULE_LICENSE("GPL-3.0"); +MODULE_AUTHOR("Makar Lill"); +MODULE_DESCRIPTION("Analogue of /dev/urandom"); + +struct gf256_gprn* global_gprn = NULL; + +static char* coeff_file = NULL; +static char* seed_file = NULL; + +static int read_file_bytes(char* filename, GF256_t* row, size_t size) +{ + if (!filename || !row) + return -EINVAL; + + struct file* f = filp_open(filename, O_RDONLY, 0); + if (IS_ERR(f)) + return PTR_ERR(f); + + loff_t pos = 0; + int res = kernel_read(f, row, size, &pos); + filp_close(f, NULL); + + if (res != size) + return (res < 0) ? res : -EIO; + + return 0; +} + +static int gprn_init_t(struct gf256_gprn* gprn) +{ + GF256_t coeff_data[POLY_DEGREE]; + GF256_t seed_data[POLY_DEGREE]; + int i; + int err; + + if (coeff_file) + { + err = read_file_bytes(coeff_file, coeff_data, sizeof(coeff_data)); + if (err != 0) + goto get_random_coeffs; + } + else + { + get_random_coeffs: + get_random_bytes(coeff_data, sizeof(coeff_data)); + for (i = 0; i < POLY_DEGREE; i++) + { + if (coeff_data[i] != 0) + break; + } + if (i == POLY_DEGREE) + coeff_data[0] = GF256_ONE; + } + + if (seed_file) + { + err = read_file_bytes(seed_file, seed_data, sizeof(seed_data)); + if (err != 0) + goto use_auto_seed; + } + else + { + use_auto_seed: + memset(seed_data, 0, sizeof(seed_data)); + for (i = 0; i < ARRAY_SIZE(GF256_DEGREES_AUTO); i++) + seed_data[GF256_DEGREES_AUTO[i]] = GF256_ONE; + } + + gf256_gprn_init_t(gprn, coeff_data, seed_data); + return 0; +} + +static ssize_t misc_read(struct file* filp, char __user* buf, size_t length, loff_t* f_pos) +{ + GF256_t kernel_buf[1024]; + size_t bytes_to_read = length; + size_t chunk; + + if (!global_gprn) + return -ENODEV; + + while (bytes_to_read > 0) + { + chunk = min(bytes_to_read, sizeof(kernel_buf)); + + gf256_gprn_generate(global_gprn, chunk, kernel_buf); + + if (copy_to_user(buf, kernel_buf, chunk)) + return -EFAULT; + + buf += chunk; + bytes_to_read -= chunk; + *f_pos += chunk; + } + + return length - bytes_to_read; +} + +static const struct file_operations fops = { + .owner = THIS_MODULE, + .read = misc_read, +}; + +static struct miscdevice prandom_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = DEVICE_NAME, + .fops = &fops, +}; + +static int __init misc_init(void) +{ + global_gprn = kzalloc(sizeof(struct gf256_gprn), GFP_KERNEL); + if (!global_gprn) + return -ENOMEM; + + if (gprn_init_t(global_gprn) != 0) + { + kfree(global_gprn); + return -EINVAL; + } + + int err = misc_register(&prandom_misc); + if (err != 0) + { + kfree(global_gprn); + return err; + } + + return 0; +} + +static void __exit misc_exit(void) +{ + kfree(global_gprn); + misc_deregister(&prandom_misc); +} + +module_param(coeff_file, charp, 0); +MODULE_PARM_DESC(coeff_file, "path to file with bytes for coefficients"); +module_param(seed_file, charp, 0); +MODULE_PARM_DESC(seed_file, "path to file with seed bytes"); + +module_init(misc_init); +module_exit(misc_exit); diff --git a/prandom.c b/prandom.c index 7d3b415..a5be38c 100644 --- a/prandom.c +++ b/prandom.c @@ -1,81 +1,81 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -/* - * prandom – misc-драйвер, предоставляющий случайные данные, аналогично /dev/urandom - * - * Copyright (C) 2025 Makar Lill - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include -#include -#include -#include - -#define DEVICE_NAME "prandom" - -static ssize_t misc_read(struct file *filp, char __user *buf, size_t length, - loff_t *f_pos) -{ - u8 kernel_buf[256]; - size_t bytes_to_read = length; - size_t chunk; - - while (bytes_to_read > 0) { - chunk = min(bytes_to_read, sizeof(kernel_buf)); - get_random_bytes(kernel_buf, chunk); - - if (copy_to_user(buf, kernel_buf, chunk)) - return -EFAULT; - - buf += chunk; - bytes_to_read -= chunk; - } - - return length; -} - -static const struct file_operations fops = { - .owner = THIS_MODULE, - .read = misc_read, -}; - -static struct miscdevice prandom_misc = { - .minor = MISC_DYNAMIC_MINOR, - .name = DEVICE_NAME, - .fops = &fops, -}; - -static int __init misc_init(void) -{ - int err = misc_register(&prandom_misc); - if (err != 0) { - return err; - } - - return 0; -} - -static void __exit misc_exit(void) -{ - misc_deregister(&prandom_misc); -} - -module_init(misc_init); -module_exit(misc_exit); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Makar Lill"); -MODULE_DESCRIPTION("Analogue of /dev/urandom"); \ No newline at end of file +#include "prandom.h" + +#include "Galois_Field_256.h" + +void gf256_gprn_init_t(struct gf256_gprn* gprn, GF256_t* coeff_data, GF256_t* seed_data) +{ + if (!gprn) + return; + + int i; + + if (coeff_data) + { + for (i = 0; i < POLY_DEGREE; i++) + { + gprn->coeff[i] = coeff_data[i]; + } + } + else + { + for (i = 0; i < POLY_DEGREE; i++) + { + gprn->coeff[i] = (GF256_t)(i % 256); + } + } + + if (seed_data) + { + for (i = 0; i < POLY_DEGREE; i++) + { + gprn->t[i] = seed_data[i]; + } + } + else + { + memset(gprn->t, 0, POLY_DEGREE); + for (i = 0; i < ARRAY_SIZE(GF256_DEGREES_AUTO); i++) + { + gprn->t[GF256_DEGREES_AUTO[i]] = GF256_ONE; + } + } + + gprn->index = 0; +} + +GF256_t gf256_gprn_next(struct gf256_gprn* gprn) +{ + if (!gprn) + return 0; + + GF256_t new_byte = 0; + int idx; + int i; + + for (i = 0; i < POLY_DEGREE; i++) + { + if (gprn->t[i] != 0) + { + idx = (gprn->index + i) % POLY_DEGREE; + new_byte = GF256_Add(new_byte, GF256_Mul(gprn->t[i], gprn->coeff[idx])); + } + } + + // "circular" array + gprn->coeff[gprn->index] = new_byte; + gprn->index = (gprn->index + 1) % POLY_DEGREE; + + return new_byte; +} + +void gf256_gprn_generate(struct gf256_gprn* gprn, size_t count, GF256_t* output) +{ + if (!gprn || !output || count == 0) + return; + + size_t i; + for (i = 0; i < count; i++) + { + output[i] = gf256_gprn_next(gprn); + } +} diff --git a/prandom.h b/prandom.h new file mode 100644 index 0000000..79c86f1 --- /dev/null +++ b/prandom.h @@ -0,0 +1,36 @@ +#ifndef PRANDOM_H +#define PRANDOM_H + +#ifdef __KERNEL__ +#include +#else +#include +#endif + +#ifdef __KERNEL__ +#include +#else +#include +#endif +#include "Galois_Field_256.h" + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) +#endif + +#define POLY_DEGREE 256 +#define GF256_ONE (GF256_t)0x01 +static const int GF256_DEGREES_AUTO[] = {0, 2, 5, 10, 255}; + +struct gf256_gprn +{ + GF256_t coeff[POLY_DEGREE]; + GF256_t t[POLY_DEGREE]; + int index; +}; + +void gf256_gprn_init_t(struct gf256_gprn* gprn, GF256_t* coeff_data, GF256_t* seed_data); +GF256_t gf256_gprn_next(struct gf256_gprn* gprn); +void gf256_gprn_generate(struct gf256_gprn* gprn, size_t count, GF256_t* output); + +#endif diff --git a/test.c b/test.c new file mode 100644 index 0000000..758d427 --- /dev/null +++ b/test.c @@ -0,0 +1,180 @@ +#include +#include +#include +#include + +#include "Galois_Field_256.h" +#include "prandom.h" + +#define NUM_BYTES 10 + +static int read_file(const char* path, unsigned char* buf, size_t size) +{ + FILE* f = fopen(path, "rb"); + if (!f) + return -1; + + size_t read = fread(buf, 1, size, f); + fclose(f); + return (read == size) ? 0 : -1; +} + +static int test1_simple(void) +{ + const char* coeff_file = "assets/coeff_file.bin"; + const char* seed_file = "assets/seed_file.bin"; + const char* expected_file = "assets/expected1.bin"; + + GF256_t coeff[POLY_DEGREE]; + GF256_t seed[POLY_DEGREE]; + GF256_t generated[NUM_BYTES]; + GF256_t expected[NUM_BYTES]; + + if (read_file(coeff_file, coeff, POLY_DEGREE) != 0) + { + printf("failed to read %s\n", coeff_file); + return -1; + } + + if (read_file(seed_file, seed, POLY_DEGREE) != 0) + { + printf("failed to read %s\n", seed_file); + return -1; + } + + struct gf256_gprn gprn; + gf256_gprn_init_t(&gprn, coeff, seed); + gf256_gprn_generate(&gprn, NUM_BYTES, generated); + + if (read_file(expected_file, expected, NUM_BYTES) != 0) + { + printf("failed to read %s\n", expected_file); + return -1; + } + + for (int i = 0; i < NUM_BYTES; i++) + { + if (generated[i] != expected[i]) + { + printf("❌ Byte %d: 0x%02x != 0x%02x\n", i, generated[i], expected[i]); + return -1; + } + } + + printf("✅ Test 1 passed\n"); + return 0; +} + +static int test2_without_seed(void) +{ + const char* coeff_file = "assets/coeff_file.bin"; + const char* expected_file = "assets/expected1.bin"; + + GF256_t coeff[POLY_DEGREE]; + GF256_t generated[NUM_BYTES]; + GF256_t expected[NUM_BYTES]; + + if (read_file(coeff_file, coeff, POLY_DEGREE) != 0) + { + printf("failed to read %s\n", coeff_file); + return -1; + } + + struct gf256_gprn gprn; + gf256_gprn_init_t(&gprn, coeff, NULL); + gf256_gprn_generate(&gprn, NUM_BYTES, generated); + + if (read_file(expected_file, expected, NUM_BYTES) != 0) + { + printf("failed to read %s\n", expected_file); + return -1; + } + + for (int i = 0; i < NUM_BYTES; i++) + { + if (generated[i] != expected[i]) + { + printf("❌ Byte %d: 0x%02x != 0x%02x\n", i, generated[i], expected[i]); + return -1; + } + } + + printf("✅ Test 2 passed\n"); + return 0; +} + +// тест на равномерность чисел +static int test3_chi_squared(void) +{ + int TEST_SIZE = 1024 * 1024; + GF256_t coeff[POLY_DEGREE]; + GF256_t* data = malloc(TEST_SIZE); + if (!data) + { + printf("failed to allocate memory\n"); + return -1; + } + if (read_file("assets/coeff_file.bin", coeff, POLY_DEGREE) != 0) + { + printf("failed to read coeff_file\n"); + free(data); + return -1; + } + struct gf256_gprn gprn; + gf256_gprn_init_t(&gprn, coeff, NULL); + gf256_gprn_generate(&gprn, TEST_SIZE, data); + int observed[256] = {0}; + for (size_t i = 0; i < TEST_SIZE; i++) + { + observed[data[i]]++; + } + + double expected = TEST_SIZE / 256.0; + double chi_sq = 0.0; + for (int i = 0; i < 256; i++) + { + double diff = observed[i] - expected; + chi_sq += (diff * diff) / expected; + } + free(data); + double chi_critical = 293.0; + if (chi_sq < chi_critical) + { + printf("✅ Chi-squared test passed (χ² = %.2f < %.2f)\n", chi_sq, chi_critical); + return 0; + } + else + { + printf("❌ Chi-squared test failed (χ² = %.2f >= %.2f)\n", chi_sq, chi_critical); + return -1; + } +} + +int main(void) +{ + int result = 0; + + result = test1_simple(); + if (result != 0) + { + printf("test1 failed\n"); + return 1; + } + + result = test2_without_seed(); + if (result != 0) + { + printf("test2 failed\n"); + return 1; + } + + result = test3_chi_squared(); + if (result != 0) + { + printf("test3 failed\n"); + return 1; + } + + printf("All tests passed\n"); + return 0; +}