From 213d957ff879477e8f314cc7aea92e4a6715f450 Mon Sep 17 00:00:00 2001 From: Mikhail Romanko Date: Sat, 8 Aug 2026 20:33:32 +0300 Subject: [PATCH] Initial commit --- .github/workflows/ci.yaml | 79 ++++++++++++++++++++++++++++ .gitignore | 68 ++++++++++++++++++++++++ CMakeLists.txt | 40 ++++++++++++++ CgeTimer.h | 10 ++++ LICENSE | 12 +++++ Makefile.lint | 29 +++++++++++ Makefile.mingw | 23 +++++++++ Makefile.posix | 33 ++++++++++++ Makefile.win32 | 23 +++++++++ README.md | 106 ++++++++++++++++++++++++++++++++++++++ posix/Timer.c | 82 +++++++++++++++++++++++++++++ win32/Timer.c | 71 +++++++++++++++++++++++++ 12 files changed, 576 insertions(+) create mode 100644 .github/workflows/ci.yaml create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 CgeTimer.h create mode 100644 LICENSE create mode 100644 Makefile.lint create mode 100644 Makefile.mingw create mode 100644 Makefile.posix create mode 100644 Makefile.win32 create mode 100644 README.md create mode 100644 posix/Timer.c create mode 100644 win32/Timer.c diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..369e537 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,79 @@ +name: CI + +on: + push: + branches: [trunk] + pull_request: + branches: [trunk] + +jobs: + build-and-analyze: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install tools + run: | + sudo apt-get update -qq + sudo apt-get install -y build-essential clang clang-tools clang-tidy cppcheck + + - name: Build with GCC + run: | + make -f Makefile.posix clean + make -f Makefile.posix CC=gcc CFLAGS="-std=c99 -Wall -Wextra -Wpedantic -Werror -O2" + + - name: Build with Clang + run: | + make -f Makefile.posix clean + make -f Makefile.posix CC=clang CFLAGS="-std=c99 -Wall -Wextra -Wpedantic -Werror -O2" + + - name: Linting checks + run: | + echo "Running all linting checks..." + FAILED=0 + + # cppcheck + echo "=== Check 1/4: cppcheck ===" + if make -f Makefile.lint cppcheck; then + echo "cppcheck: PASSED" + else + echo "cppcheck: FAILED" + FAILED=1 + fi + + # clang-tidy + echo "=== Check 2/4: clang-tidy ===" + if make -f Makefile.lint clang-tidy; then + echo "clang-tidy: PASSED" + else + echo "clang-tidy: FAILED" + FAILED=1 + fi + + # scan-build + echo "=== Check 3/4: scan-build ===" + if make -f Makefile.lint scan-build; then + echo "scan-build: PASSED" + else + echo "scan-build: FAILED" + FAILED=1 + fi + + # security-check + echo "=== Check 4/4: security-check ===" + if make -f Makefile.lint security-check; then + echo "security-check: PASSED" + else + echo "security-check: FAILED" + FAILED=1 + fi + + # Final result + if [ $FAILED -ne 0 ]; then + echo "One or more linting checks failed." + exit 1 + else + echo "All linting checks passed." + fi \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f765426 --- /dev/null +++ b/.gitignore @@ -0,0 +1,68 @@ +# ---> C +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +# ---> CMake +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps +CMakeUserPresets.json + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b66c034 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.10) +project(CgeNet LANGUAGES C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) + +set(SOURCES +) + +set(SOURCES_WIN32 + src/win32/Timer.c +) + +set(SOURCES_POSIX + src/posix/Timer.c +) + +set(HEADERS + CgeTimer.h +) + +if(WIN32) + message(STATUS "Platform - Win32") + list(APPEND SOURCES ${SOURCES_WIN32}) +elseif(UNIX) + message(STATUS "Platform: Unix (Linux/BSD/MacOS X)") + list(APPEND SOURCES ${SOURCES_POSIX}) +endif() + +add_library(CgeTimer STATIC ${SOURCES} ${HEADERS}) + +target_include_directories(CgeTimer PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +install(TARGETS CgeTimer + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin +) + +install(FILES ${HEADERS} DESTINATION include) diff --git a/CgeTimer.h b/CgeTimer.h new file mode 100644 index 0000000..ba15f6d --- /dev/null +++ b/CgeTimer.h @@ -0,0 +1,10 @@ +#ifndef CGE_TIMER_H +#define CGE_TIMER_H + +#include + +int CgeTimerIsMonotonic(void); +uint64_t CgeTimerNanoseconds(void); +uint64_t CgeTimerMilliseconds(void); + +#endif /* CGE_TIMER_H */ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3d44ffd --- /dev/null +++ b/LICENSE @@ -0,0 +1,12 @@ +Copyright (C) 2026 by blankhex me@blankhex.com + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile.lint b/Makefile.lint new file mode 100644 index 0000000..cac9110 --- /dev/null +++ b/Makefile.lint @@ -0,0 +1,29 @@ +# Makefile.lint - Individual linting targets + +.PHONY: all +all: cppcheck clang-tidy scan-build security-check + @echo "All linting scheduled" + +.PHONY: cppcheck +cppcheck: + @echo "Running cppcheck..." + @cppcheck --enable=warning,performance,portability --std=c99 --quiet -Iinclude -I. . + +.PHONY: clang-tidy +clang-tidy: + @echo "Running clang-tidy..." + @for file in $$(find . -name "*.c" -type f -not -path "*/win32/*"); do \ + clang-tidy "$$file" -- -I. -Iinclude -std=c99; \ + done + +.PHONY: scan-build +scan-build: + @echo "Running scan-build..." + @scan-build --status-bugs make -f Makefile.posix clean all CC=clang + +.PHONY: security-check +security-check: + @echo "Scanning for unsafe C functions..." + @grep -rnE '\b(strcpy|strcat|sprintf|gets|scanf|sscanf|realpath|mktemp|tempnam|tmpnam|getwd|getlogin)\b' \ + . --include="*.c" --include="*.h" | grep -v "^Binary file" && \ + exit 1 || exit 0 \ No newline at end of file diff --git a/Makefile.mingw b/Makefile.mingw new file mode 100644 index 0000000..08f18ac --- /dev/null +++ b/Makefile.mingw @@ -0,0 +1,23 @@ +# MinGW Makefile for CgeTimer + +CC = gcc +AR = ar +CFLAGS = -std=c99 -O2 -Wall -Wextra +ARFLAGS = rcs +TARGET = libCgeTimer.a + +SOURCES = win32/Timer.c +OBJECTS = $(SOURCES:.c=.o) + +.PHONY: all clean + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(AR) $(ARFLAGS) $@ $^ + +%.o: %.c CgeTimer.h + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + del $(OBJECTS) $(TARGET) 2>nul || exit 0 diff --git a/Makefile.posix b/Makefile.posix new file mode 100644 index 0000000..ee08122 --- /dev/null +++ b/Makefile.posix @@ -0,0 +1,33 @@ +# POSIX Makefile for CgeTimer + +CC = gcc +AR = ar +CFLAGS = -std=c99 -O2 -Wall -Wextra -fPIC +ARFLAGS = rcs +TARGET = libCgeTimer.a + +SOURCES = posix/Timer.c +OBJECTS = $(SOURCES:.c=.o) + +.PHONY: all clean install + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(AR) $(ARFLAGS) $@ $^ + +%.o: %.c CgeTimer.h + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -f $(OBJECTS) $(TARGET) + +install: $(TARGET) + cp $(TARGET) /usr/local/lib/ + cp CgeTimer.h /usr/local/include/ + ldconfig || echo "Run ldconfig manually if needed" + +uninstall: + rm -f /usr/local/lib/libCgeTimer.a + rm -f /usr/local/include/CgeTimer.h + ldconfig || true diff --git a/Makefile.win32 b/Makefile.win32 new file mode 100644 index 0000000..3856a06 --- /dev/null +++ b/Makefile.win32 @@ -0,0 +1,23 @@ +# Makefile.win32 for MSVC (NMake) +# Usage: Open "x86 Native Tools Command Prompt", then: +# nmake -f Makefile.win32 + +CC = cl +LIB = lib +CFLAGS = /c /nologo /W3 /O2 +LIBFLAGS = /nologo +TARGET = CgeTimer.lib + +SOURCES = win32/Timer.c +OBJECTS = $(SOURCES:.c=.obj) + +$(TARGET): $(OBJECTS) + $(LIB) $(LIBFLAGS) /OUT:$(TARGET) $(OBJECTS) + +{.}.c{}.obj: + $(CC) $(CFLAGS) /Fo$@ $< + +clean: + del $(OBJECTS) $(TARGET) 2>nul + +.PHONY: clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..cb84bb7 --- /dev/null +++ b/README.md @@ -0,0 +1,106 @@ +# CgeNet - Network Access Library for C + +A lightweight, dependency‑free C library providing a consistent interface for +socket operations across POSIX platforms. The library abstracts the low‑level +socket API and offers a unified, easy‑to‑use set of functions for creating, +configuring, and using TCP/UDP sockets. + +## Features + +- Cross‑platform support (Linux, macOS, Windows) +- IPv4 and IPv6 address handling +- TCP and UDP socket types +- Non‑blocking and blocking mode control +- Simple polling interface for readable / writable events +- Comprehensive error handling with explicit error codes +- Context pointer attached to each socket for user data +- No dynamic memory allocation required by the user (library manages its own + resources) + +## Usage Example + +```c +#include "CgeNet.h" +#include +#include +#include +#include + +int main(void) { + int result; + + #ifdef SIGPIPE + signal(SIGPIPE, SIG_IGN); + #endif + + if (!CgeNetInit()) { + fprintf(stderr, "Failed to initialize network"); + return 1; + } + + CgeNetSocket *sock = CgeNetOpen(CGE_NET_IPV4, CGE_NET_TCP, &result); + if (!sock) { + fprintf(stderr, "Failed to open socket: %d\n", result); + CgeNetCleanup(); + return 1; + } + + CgeNetAddr addr; + if (!CgeNetAddrFromStr("127.0.0.1", 8080, &addr)) { + fprintf(stderr, "Invalid address\n"); + CgeNetClose(sock); + CgeNetCleanup(); + return 1; + } + + if (!CgeNetConnect(sock, &addr)) { + fprintf(stderr, "Connect failed: %d\n", CgeNetErrorCode(sock)); + CgeNetClose(sock); + CgeNetCleanup(); + return 1; + } + + const char *msg = "Hello, server!"; + size_t written; + if (!CgeNetWrite(sock, msg, strlen(msg), &written)) { + fprintf(stderr, "Write failed: %d\n", CgeNetErrorCode(sock)); + CgeNetClose(sock); + CgeNetCleanup(); + return 1; + } + + char buffer[256]; + size_t actual; + if (CgeNetRead(sock, buffer, sizeof(buffer) - 1, &actual)) { + buffer[actual] = '\0'; + printf("Received: %s\n", buffer); + } + + CgeNetClose(sock); + CgeNetCleanup(); + + return 0; +} +``` + +## Build Systems + +- **CMake**: supports Linux, macOS, Windows (MSVC, MinGW) +- **Makefile.posix**: for GCC/Clang on POSIX systems +- **Makefile.mingw**: for MinGW on Windows +- **Makefile.win32**: for MSVC with NMake + +Builds a static library. No shared library or external dependencies + +## Portability + +- **Standard compliance**: written in C89 + `stdint.h` +- **No external dependencies**: only standard C and system socket headers and + libraries are used +- **Signal handling**: Users of the library should ignore `SIGPIPE` to prevent + the process from being terminated when writing to a closed socket. This can be + done once in the application, e.g. `signal(SIGPIPE, SIG_IGN);` + +## License + +0BSD - a permissive license with no attribution required. diff --git a/posix/Timer.c b/posix/Timer.c new file mode 100644 index 0000000..235bc52 --- /dev/null +++ b/posix/Timer.c @@ -0,0 +1,82 @@ +#define _POSIX_C_SOURCE 200112L +#include "../CgeTimer.h" +#include +#include +#include +#include + +enum Type { + TYPE_UNDEFINED, + TYPE_OLD_REALTIME, + TYPE_NEW_REALTIME, + TYPE_MONOTONIC +}; + +static int timerType = TYPE_UNDEFINED; + +static int checkAvailableTimer(void) { +#if (_POSIX_TIMERS > 0) + struct timespec ts; +#endif + + if (timerType != TYPE_UNDEFINED) + return timerType; + +#if (_POSIX_TIMERS > 0) + if (!clock_gettime(CLOCK_MONOTONIC, &ts)) { + timerType = TYPE_MONOTONIC; + } else if (!clock_gettime(CLOCK_REALTIME, &ts)) { + timerType = TYPE_NEW_REALTIME; + } else +#endif + timerType = TYPE_OLD_REALTIME; + return timerType; +} + +static void getTimer(uint64_t *sec, uint64_t *nsec) { +#if (_POSIX_TIMERS > 0) + struct timespec ts; +#endif + struct timeval tv; + + switch (checkAvailableTimer()) { +#if (_POSIX_TIMERS > 0) + case TYPE_MONOTONIC: + clock_gettime(CLOCK_MONOTONIC, &ts); + *sec = ts.tv_sec; + *nsec = ts.tv_nsec; + break; + + case TYPE_NEW_REALTIME: + clock_gettime(CLOCK_REALTIME, &ts); + *sec = ts.tv_sec; + *nsec = ts.tv_nsec; + break; +#endif + + default: + case TYPE_OLD_REALTIME: + gettimeofday(&tv, NULL); + *sec = tv.tv_sec; + *nsec = tv.tv_usec * (uint64_t)1000; + break; + } +} + +int CgeTimerIsMonotonic(void) { + return checkAvailableTimer() == TYPE_MONOTONIC; +} + +uint64_t BH_TimerMilliseconds(void) { + uint64_t newSec, newNsec; + + getTimer(&newSec, &newNsec); + return (newSec * 1000u) + (newNsec / 1000000ul); +} + +uint64_t BH_TimerNanoseconds(void) { + uint64_t newSec, newNsec; + + getTimer(&newSec, &newNsec); + return (newSec * 1000000000ul) + newNsec; +} diff --git a/win32/Timer.c b/win32/Timer.c new file mode 100644 index 0000000..9676e5d --- /dev/null +++ b/win32/Timer.c @@ -0,0 +1,71 @@ +#define WIN32_LEAN_AND_MEAN +#include "../CgeTimer.h" +#include +#include + +enum Type { + TYPE_UNDEFINED, + TYPE_TICKS, + TYPE_COUNTER, +}; + +static int timerType = TYPE_UNDEFINED; +static LARGE_INTEGER timerFrequency; + +static int checkAvailableTimer(void) { + LARGE_INTEGER dummy; + + if (timerType != TYPE_UNDEFINED) + return timerType; + + if (QueryPerformanceFrequency(&timerFrequency)) { + if (QueryPerformanceCounter(&dummy)) { + timerType = TYPE_COUNTER; + } else { + timerType = TYPE_TICKS; + } + } else { + timerType = TYPE_TICKS; + } + + return timerType; +} + +static void getTimer(uint64_t *sec, uint64_t *nsec) { + LARGE_INTEGER newCount; + uint64_t newTicks; + + switch (checkAvailableTimer()) + { + case TYPE_COUNTER: + QueryPerformanceCounter(&newCount); + *sec = newCount.QuadPart / timerFrequency->QuadPart; + *nsec = (newCount.QuadPart - *sec * timerFrequency->QuadPart) * 1000000000ul / timerFrequency->QuadPart; + break; + + default: + case TYPE_TICKS: + newTicks = GetTickCount64(); + *sec = newTicks / 1000; + *nsec = (newTicks - *sec * 1000) * 1000000ul; + break; + } +} + +int CgeTimerIsMonotonic(void) { + return checkAvailableTimer() == TYPE_COUNTER; +} + +uint64_t BH_TimerMilliseconds(void) { + uint64_t newSec, newNsec; + + getTimer(&newSec, &newNsec); + return (newSec * 1000u) + (newNsec / 1000000ul); +} + +uint64_t BH_TimerNanoseconds(void) { + uint64_t newSec, newNsec; + + getTimer(&newSec, &newNsec); + return (newSec * 1000000000ul) + newNsec; +}