├── ipv6-parse.png ├── .editorconfig ├── cmake_vs2015.bat ├── cmake_vs2017.bat ├── cmake ├── ipv6-parse-config.cmake.in └── emscripten.cmake ├── .travis.yml ├── ipv6_test_config.h.in ├── test_package ├── CMakeLists.txt ├── test_ipv6_parse.c └── conanfile.py ├── .eslintignore ├── cmake_gmake.sh ├── ipv6-parse.pc.in ├── debian ├── changelog ├── control └── copyright ├── vcpkg ├── vcpkg.json ├── usage └── portfile.cmake ├── ipv6_config.h.in ├── codecov.yml ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── pages.yml │ ├── ci.yml │ └── release.yml ├── .npmignore ├── .gitignore ├── LICENSE ├── docs ├── README.md ├── test.html └── diagnostic.html ├── .eslintrc.json ├── valgrind.supp ├── makedoc.py ├── test ├── check-wasm.js ├── test-node.js ├── test-wasm.js ├── test-sync-api.js ├── validate-build.js ├── test-api.js └── test-performance.js ├── run_coverage.sh ├── ipv6-parse.spec ├── package.json ├── test_all.c ├── coverage_analysis.md ├── conanfile.py ├── cmdline.c ├── README_CONAN.md ├── Formula └── ipv6-parse.rb ├── test.sh ├── test_wasm_node.js ├── index.js ├── README_COVERAGE.md ├── index.d.ts ├── CONTRIBUTING.md ├── README_NPM.md ├── README_CI.md ├── ipv6_wasm.c ├── ipv6.h ├── ROADMAP_RELEASES.md └── CMakeLists.txt /ipv6-parse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jrepp/ipv6-parse/HEAD/ipv6-parse.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{cc,h}] 4 | indent_style = space 5 | indent_size = 4 6 | trim_trailing_whitespace = true 7 | -------------------------------------------------------------------------------- /cmake_vs2015.bat: -------------------------------------------------------------------------------- 1 | mkdir build_vs2015 2 | pushd build_vs2015 3 | cmake --debug-trycompile %* -G "Visual Studio 14 2015 Win64" .. 4 | popd 5 | -------------------------------------------------------------------------------- /cmake_vs2017.bat: -------------------------------------------------------------------------------- 1 | mkdir build_vs2017 2 | pushd build_vs2017 3 | cmake --debug-trycompile %* -G "Visual Studio 15 2017 Win64" .. 4 | popd 5 | -------------------------------------------------------------------------------- /cmake/ipv6-parse-config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | # ipv6-parse CMake package configuration file 4 | 5 | include("${CMAKE_CURRENT_LIST_DIR}/ipv6-parse-targets.cmake") 6 | 7 | check_required_components(ipv6-parse) 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: c 3 | script: 4 | - mkdir build 5 | - cd build 6 | - cmake -G "Unix Makefiles" .. 7 | - make all 8 | - bin/ipv6-test 9 | 10 | os: 11 | - linux 12 | - osx 13 | 14 | -------------------------------------------------------------------------------- /ipv6_test_config.h.in: -------------------------------------------------------------------------------- 1 | // For test code only 2 | #cmakedefine HAVE_WINSOCK_2_H 1 3 | #cmakedefine HAVE_WS_2_TCPIP_H 1 4 | #cmakedefine HAVE_SYS_SOCKET_H 1 5 | #cmakedefine HAVE_NETINET_IN_H 1 6 | #cmakedefine HAVE_ARPA_INET_H 1 7 | #cmakedefine HAVE_ASSERT_H 1 8 | -------------------------------------------------------------------------------- /test_package/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(test_ipv6_parse C) 3 | 4 | find_package(ipv6-parse REQUIRED) 5 | 6 | add_executable(test_ipv6_parse test_ipv6_parse.c) 7 | target_link_libraries(test_ipv6_parse ipv6-parse::ipv6-parse) 8 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Build directories 2 | build/ 3 | build_wasm/ 4 | build_test/ 5 | bin/ 6 | 7 | # Node modules 8 | node_modules/ 9 | 10 | # Generated WASM files (Emscripten output) 11 | docs/ipv6-parse.js 12 | 13 | # Coverage 14 | coverage/ 15 | 16 | # Other 17 | *.min.js 18 | -------------------------------------------------------------------------------- /cmake_gmake.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | mkdir -p build_gmake 6 | pushd build_gmake || exit 7 | 8 | # -DCMAKE_RULE_MESSAGES:BOOL=OFF -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON 9 | cmake -DCMAKE_BUILD_TYPE=Debug -G "Unix Makefiles" .. 10 | # make --no-print-directory 11 | 12 | popd || exit 13 | -------------------------------------------------------------------------------- /ipv6-parse.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | exec_prefix=${prefix} 3 | libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ 4 | includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ 5 | 6 | Name: ipv6-parse 7 | Description: IPv6/IPv4 address parser with full RFC compliance (RFC 4291, RFC 5952, RFC 4007) 8 | Version: @PROJECT_VERSION@ 9 | URL: https://github.com/jrepp/ipv6-parse 10 | 11 | Cflags: -I${includedir} 12 | Libs: -L${libdir} -lipv6-parse 13 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | ipv6-parse (1.2.1-1) unstable; urgency=medium 2 | 3 | * Initial Debian package release 4 | * Full RFC compliance (RFC 4291, RFC 5952, RFC 4007) 5 | * Support for CIDR masks, ports, and zone IDs 6 | * IPv4-mapped and IPv4-compatible address handling 7 | * High-performance C implementation 8 | * Shared and static library support 9 | * pkg-config integration 10 | * CMake package configuration 11 | 12 | -- Jacob Repp Wed, 12 Nov 2025 00:00:00 +0000 13 | -------------------------------------------------------------------------------- /vcpkg/vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ipv6-parse", 3 | "version-string": "1.2.1", 4 | "description": "Fast, RFC-compliant IPv6/IPv4 address parser with CIDR, port, and zone ID support", 5 | "homepage": "https://github.com/jrepp/ipv6-parse", 6 | "license": "MIT", 7 | "supports": "!(uwp | arm)", 8 | "dependencies": [ 9 | { 10 | "name": "vcpkg-cmake", 11 | "host": true 12 | }, 13 | { 14 | "name": "vcpkg-cmake-config", 15 | "host": true 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /test_package/test_ipv6_parse.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(void) { 6 | ipv6_address_full_t addr; 7 | const char *test_addr = "2001:db8::1"; 8 | 9 | if (ipv6_from_str(test_addr, strlen(test_addr), &addr)) { 10 | char buffer[IPV6_STRING_SIZE]; 11 | ipv6_to_str(&addr, buffer, sizeof(buffer)); 12 | printf("Successfully parsed IPv6 address: %s\n", buffer); 13 | return 0; 14 | } 15 | 16 | printf("Failed to parse IPv6 address\n"); 17 | return 1; 18 | } 19 | -------------------------------------------------------------------------------- /vcpkg/usage: -------------------------------------------------------------------------------- 1 | ipv6-parse provides CMake targets: 2 | 3 | find_package(ipv6-parse CONFIG REQUIRED) 4 | target_link_libraries(main PRIVATE ipv6-parse::ipv6-parse) 5 | 6 | ipv6-parse also provides pkg-config: 7 | 8 | pkg_check_modules(IPV6_PARSE REQUIRED IMPORTED_TARGET ipv6-parse) 9 | target_link_libraries(main PRIVATE PkgConfig::IPV6_PARSE) 10 | 11 | Command-line tool: 12 | 13 | ipv6-parse-cli "2001:db8::1/64" 14 | ipv6-parse-cli "[::1]:8080" 15 | ipv6-parse-cli "fe80::1%eth0" 16 | 17 | For more information, see: 18 | https://github.com/jrepp/ipv6-parse 19 | -------------------------------------------------------------------------------- /ipv6_config.h.in: -------------------------------------------------------------------------------- 1 | #cmakedefine HAVE_MALLOC_H 1 2 | #cmakedefine HAVE_ALLOCA_H 1 3 | #cmakedefine HAVE_STRING_H 1 4 | #cmakedefine HAVE_STDIO_H 1 5 | #cmakedefine HAVE_STDARG_H 1 6 | #cmakedefine HAVE__SNPRINTF_S 1 7 | 8 | #if WIN32 9 | #pragma warning(disable: 4820) // Disable alignment errors in windows headers 10 | #pragma warning(disable: 4255) // Disable prototype () -> (void) conversion warning 11 | #pragma warning(disable: 5045) // Disable /Qspectre mitigation info warnings 12 | #pragma warning(disable: 4061) // Disable specified enumerator identifier is not explicitly handled by a case level 13 | #pragma warning(disable: 4034) 14 | #endif 15 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: yes 3 | 4 | coverage: 5 | precision: 2 6 | round: down 7 | range: "70...100" 8 | 9 | status: 10 | project: 11 | default: 12 | target: 85% 13 | threshold: 1% 14 | if_ci_failed: error 15 | 16 | patch: 17 | default: 18 | target: 80% 19 | threshold: 2% 20 | 21 | comment: 22 | layout: "reach,diff,flags,tree,footer" 23 | behavior: default 24 | require_changes: false 25 | require_base: no 26 | require_head: yes 27 | 28 | ignore: 29 | - "test.c" 30 | - "test_extended.c" 31 | - "fuzz.c" 32 | - "cmdline.c" 33 | - "build/**/*" 34 | - "cmake-build-*/**/*" 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /test_package/conanfile.py: -------------------------------------------------------------------------------- 1 | import os 2 | from conan import ConanFile 3 | from conan.tools.cmake import CMake, cmake_layout 4 | from conan.tools.build import can_run 5 | 6 | 7 | class Ipv6ParseTestConan(ConanFile): 8 | settings = "os", "compiler", "build_type", "arch" 9 | generators = "CMakeDeps", "CMakeToolchain" 10 | 11 | def requirements(self): 12 | self.requires(self.tested_reference_str) 13 | 14 | def layout(self): 15 | cmake_layout(self) 16 | 17 | def build(self): 18 | cmake = CMake(self) 19 | cmake.configure() 20 | cmake.build() 21 | 22 | def test(self): 23 | if can_run(self): 24 | cmd = os.path.join(self.cpp.build.bindir, "test_ipv6_parse") 25 | self.run(cmd, env="conanrun") 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Build directories 2 | build/ 3 | build_wasm/ 4 | build_test/ 5 | bin/ 6 | *.o 7 | *.a 8 | *.so 9 | *.dylib 10 | *.dll 11 | 12 | # Source files (ship only WASM) 13 | *.c 14 | !ipv6.c 15 | !ipv6_wasm.c 16 | *.h 17 | !ipv6.h 18 | cmdline.c 19 | test.c 20 | test_all.c 21 | test_extended.c 22 | fuzz.c 23 | 24 | # CMake files 25 | CMakeLists.txt 26 | CMakeCache.txt 27 | CMakeFiles/ 28 | cmake_install.cmake 29 | Makefile 30 | *.cmake 31 | !cmake/emscripten.cmake 32 | 33 | # Git files 34 | .git/ 35 | .gitignore 36 | .github/ 37 | 38 | # Documentation (except main README and WASM docs) 39 | TECHNICAL_REVIEW_WASM_API.md 40 | WASM_IMPLEMENTATION_SUMMARY.md 41 | ROADMAP_RELEASES.md 42 | PR_STAGING_PLAN.md 43 | QUICK_START_STAGED_PRS.md 44 | docs/README.md 45 | docs/index.html 46 | 47 | # Scripts 48 | stage_prs.sh 49 | build_wasm.sh 50 | 51 | # Test files 52 | test/ 53 | 54 | # Coverage 55 | *.gcda 56 | *.gcno 57 | *.gcov 58 | coverage/ 59 | 60 | # IDE 61 | .vscode/ 62 | .idea/ 63 | *.swp 64 | *.swo 65 | *~ 66 | 67 | # OS 68 | .DS_Store 69 | Thumbs.db 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build directories 2 | build*/ 3 | build/ 4 | bin/ 5 | cmake-build-*/ 6 | 7 | # IDE and OS files 8 | *.swp 9 | *.swo 10 | .idea/ 11 | .vscode/ 12 | *.sublime-* 13 | .DS_Store 14 | Thumbs.db 15 | 16 | # Dependencies 17 | node_modules/ 18 | 19 | # Coverage files 20 | *.gcov 21 | *.gcda 22 | *.gcno 23 | *.gcno.* 24 | *.gcda.* 25 | 26 | # Object files and libraries 27 | *.o 28 | *.a 29 | *.so 30 | *.so.* 31 | *.dylib 32 | *.lib 33 | *.dll 34 | *.exe 35 | 36 | # Test binaries 37 | ipv6-test 38 | ipv6-test-* 39 | ipv6-cmd 40 | ipv6-fuzz 41 | test-cov 42 | test-ext-cov 43 | test_results.md 44 | fuzz_results.md 45 | 46 | # Generated config and headers 47 | ipv6_config.h 48 | ipv6_test_config.h 49 | 50 | # Debug symbols 51 | *.dSYM/ 52 | 53 | # Package build artifacts 54 | *.deb 55 | *.rpm 56 | *.tar.gz 57 | *.zip 58 | _CPack_Packages/ 59 | 60 | # CMake generated files 61 | CMakeCache.txt 62 | CMakeFiles/ 63 | Makefile 64 | cmake_install.cmake 65 | install_manifest.txt 66 | 67 | # pkg-config generated file (but not .pc.in templates) 68 | ipv6-parse.pc 69 | 70 | # Conan 71 | CMakeUserPresets.json 72 | test_package/build/ 73 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2025 Jacob Repp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # IPv6 Address Parser - WebAssembly Demo 2 | 3 | This directory contains the WebAssembly build of ipv6-parse for use in web browsers. 4 | 5 | ## Files 6 | 7 | - `index.html` - Interactive demo page 8 | - `ipv6-parse.js` - WebAssembly module with JavaScript glue code (generated by build) 9 | 10 | ## Accessing the Demo 11 | 12 | When deployed to GitHub Pages, the demo will be accessible at: 13 | https://jrepp.github.io/ipv6-parse/ 14 | 15 | ## Building 16 | 17 | To regenerate these files: 18 | 19 | ```bash 20 | # From the repository root 21 | ./build_wasm.sh 22 | ``` 23 | 24 | See [README_WASM.md](../README_WASM.md) for detailed build instructions. 25 | 26 | ## Features 27 | 28 | The demo page allows you to: 29 | 30 | - Parse IPv6 and IPv4 addresses 31 | - View formatted output (RFC 5952 compliant) 32 | - Inspect address components (hexadecimal) 33 | - See parsed flags (port, CIDR mask, zone ID, IPv4 embedding) 34 | - Test various address formats interactively 35 | 36 | ## Browser Compatibility 37 | 38 | - Chrome 57+ 39 | - Firefox 52+ 40 | - Safari 11+ 41 | - Edge 16+ 42 | 43 | ## Source Code 44 | 45 | View the source code on GitHub: https://github.com/jrepp/ipv6-parse 46 | 47 | ## License 48 | 49 | MIT License - See [LICENSE](../LICENSE) for details 50 | -------------------------------------------------------------------------------- /vcpkg/portfile.cmake: -------------------------------------------------------------------------------- 1 | # vcpkg portfile for ipv6-parse 2 | # To use: Copy this directory to vcpkg/ports/ipv6-parse and run: vcpkg install ipv6-parse 3 | 4 | vcpkg_from_github( 5 | OUT_SOURCE_PATH SOURCE_PATH 6 | REPO jrepp/ipv6-parse 7 | REF v1.2.1 8 | SHA512 0 # Will be computed by vcpkg 9 | HEAD_REF master 10 | ) 11 | 12 | vcpkg_cmake_configure( 13 | SOURCE_PATH "${SOURCE_PATH}" 14 | OPTIONS 15 | -DIPV6_PARSE_LIBRARY_ONLY=ON 16 | -DENABLE_COVERAGE=OFF 17 | -DPARSE_TRACE=OFF 18 | ) 19 | 20 | vcpkg_cmake_build() 21 | 22 | vcpkg_cmake_install() 23 | 24 | vcpkg_cmake_config_fixup(CONFIG_PATH lib/cmake/ipv6-parse) 25 | 26 | vcpkg_fixup_pkgconfig() 27 | 28 | # Remove duplicate files 29 | file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") 30 | file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/share") 31 | 32 | # Install command-line tool 33 | if(EXISTS "${CURRENT_PACKAGES_DIR}/bin/ipv6-parse-cli${VCPKG_TARGET_EXECUTABLE_SUFFIX}") 34 | vcpkg_copy_tools(TOOL_NAMES ipv6-parse-cli AUTO_CLEAN) 35 | endif() 36 | 37 | # Handle copyright 38 | vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE") 39 | 40 | # Copy usage file 41 | file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") 42 | -------------------------------------------------------------------------------- /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - 'docs/**' 9 | - 'ipv6_wasm.c' 10 | - 'build_wasm.sh' 11 | - '.github/workflows/pages.yml' 12 | workflow_dispatch: 13 | 14 | permissions: 15 | contents: read 16 | pages: write 17 | id-token: write 18 | 19 | concurrency: 20 | group: "pages" 21 | cancel-in-progress: false 22 | 23 | jobs: 24 | build-and-deploy: 25 | name: Build WASM and Deploy to GitHub Pages 26 | runs-on: ubuntu-latest 27 | environment: 28 | name: github-pages 29 | url: ${{ steps.deployment.outputs.page_url }} 30 | steps: 31 | - name: Checkout code 32 | uses: actions/checkout@v4 33 | 34 | - name: Setup Emscripten 35 | uses: mymindstorm/setup-emsdk@v14 36 | with: 37 | version: latest 38 | 39 | - name: Build WASM 40 | run: ./build_wasm.sh 41 | 42 | - name: Setup Pages 43 | uses: actions/configure-pages@v4 44 | 45 | - name: Upload artifact 46 | uses: actions/upload-pages-artifact@v3 47 | with: 48 | path: 'docs' 49 | 50 | - name: Deploy to GitHub Pages 51 | id: deployment 52 | uses: actions/deploy-pages@v4 53 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: ipv6-parse 2 | Section: libs 3 | Priority: optional 4 | Maintainer: Jacob Repp 5 | Build-Depends: debhelper-compat (= 13), cmake (>= 3.12) 6 | Standards-Version: 4.6.0 7 | Homepage: https://github.com/jrepp/ipv6-parse 8 | Vcs-Git: https://github.com/jrepp/ipv6-parse.git 9 | Vcs-Browser: https://github.com/jrepp/ipv6-parse 10 | 11 | Package: libipv6-parse1 12 | Architecture: any 13 | Depends: ${shlibs:Depends}, ${misc:Depends} 14 | Description: IPv6/IPv4 address parser library 15 | High-performance IPv6/IPv4 address parser with full RFC compliance 16 | (RFC 4291, RFC 5952, RFC 4007). 17 | . 18 | Features: 19 | - Full RFC compliance for IPv6 and IPv4 address parsing 20 | - Support for CIDR masks, ports, and zone IDs 21 | - IPv4-mapped and IPv4-compatible address handling 22 | - High-performance C implementation 23 | - Zero dependencies 24 | 25 | Package: libipv6-parse-dev 26 | Architecture: any 27 | Section: libdevel 28 | Depends: libipv6-parse1 (= ${binary:Version}), ${misc:Depends} 29 | Description: Development files for IPv6/IPv4 address parser 30 | This package contains the development files (headers and static library) 31 | for libipv6-parse, a high-performance IPv6/IPv4 address parser. 32 | . 33 | Install this package if you want to develop applications using libipv6-parse. 34 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: ipv6-parse 3 | Upstream-Contact: Jacob Repp 4 | Source: https://github.com/jrepp/ipv6-parse 5 | 6 | Files: * 7 | Copyright: 2017-2025 Jacob Repp 8 | License: MIT 9 | 10 | License: MIT 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | . 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | . 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "es2021": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "parserOptions": { 9 | "ecmaVersion": 2021, 10 | "sourceType": "module" 11 | }, 12 | "rules": { 13 | "indent": ["error", 2], 14 | "linebreak-style": ["error", "unix"], 15 | "quotes": ["error", "single", { "avoidEscape": true }], 16 | "semi": ["error", "always"], 17 | "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], 18 | "no-console": "off", 19 | "no-trailing-spaces": "error", 20 | "eol-last": ["error", "always"], 21 | "object-curly-spacing": ["error", "always"], 22 | "array-bracket-spacing": ["error", "never"], 23 | "comma-dangle": ["error", "never"], 24 | "arrow-spacing": ["error", { "before": true, "after": true }], 25 | "keyword-spacing": ["error", { "before": true, "after": true }], 26 | "space-before-blocks": ["error", "always"], 27 | "space-before-function-paren": ["error", { 28 | "anonymous": "always", 29 | "named": "never", 30 | "asyncArrow": "always" 31 | }], 32 | "space-in-parens": ["error", "never"], 33 | "space-infix-ops": "error", 34 | "prefer-const": "error", 35 | "no-var": "error" 36 | }, 37 | "overrides": [ 38 | { 39 | "files": ["docs/ipv6-parse.js"], 40 | "rules": { 41 | "no-unused-vars": "off", 42 | "no-undef": "off" 43 | } 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /valgrind.supp: -------------------------------------------------------------------------------- 1 | # Valgrind suppression file for ipv6-parse 2 | # 3 | # Status: No suppressions currently needed (as of 2025-11-08) 4 | # 5 | # The ipv6-parse library is clean with no memory leaks or uninitialized 6 | # value warnings. Valgrind runs show: 7 | # - ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) 8 | # - All heap blocks were freed -- no leaks are possible 9 | # 10 | # Previously, this file contained broad suppressions for: 11 | # 1. libc conditional jumps 12 | # 2. libc uninitialized values 13 | # 3. C++ std::string (despite this being a C library with no C++ usage) 14 | # 15 | # These suppressions were: 16 | # - Never actually triggered (suppressed count always 0) 17 | # - Too broad and could hide real bugs 18 | # - Unnecessary given the clean Valgrind results 19 | # 20 | # If future Valgrind warnings appear: 21 | # 1. First investigate if they are real bugs in ipv6-parse 22 | # 2. Only add suppressions for confirmed false positives 23 | # 3. Make suppressions as specific as possible (include function names) 24 | # 4. Document the reason with a reference to the specific issue 25 | # 5. Link to bug report or discussion explaining the false positive 26 | # 27 | # Example of a properly documented suppression: 28 | # { 29 | # brief_description_of_false_positive 30 | # Memcheck:Leak 31 | # fun:specific_function_name 32 | # fun:call_stack_frame 33 | # # Reason: Brief explanation of why this is a false positive 34 | # # Reference: Link to bug report or discussion 35 | # } 36 | -------------------------------------------------------------------------------- /makedoc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import re 4 | import sys 5 | 6 | from contextlib import contextmanager 7 | 8 | def start_section(line): 9 | sys.stdout.write("\n{}".format(line[3:])) 10 | 11 | 12 | def emit_line(line): 13 | sys.stdout.write("{}".format(line[3:])) 14 | 15 | def emit_line_nosub(line): 16 | sys.stdout.write(line) 17 | 18 | def emit_empty(): 19 | sys.stdout.write("\n") 20 | 21 | ## Global state to track code section beg/end 22 | in_code_section = False 23 | 24 | def process_line(line): 25 | global in_code_section 26 | 27 | section_match = re.compile(r'^// #') 28 | line_match = re.compile(r'^// ') 29 | space_match = re.compile(r'^//$') 30 | code_section_match = re.compile(r'^// ~~~~') 31 | m = section_match.match(line) 32 | if m: 33 | start_section(line) 34 | elif code_section_match.match(line): 35 | if not in_code_section: 36 | emit_line_nosub('```c') 37 | emit_empty() 38 | else: 39 | emit_line_nosub('```') 40 | emit_empty() 41 | in_code_section = not in_code_section 42 | elif line_match.match(line): 43 | emit_line(line) 44 | elif space_match.match(line): 45 | emit_empty() 46 | elif in_code_section: 47 | emit_line_nosub(line) 48 | 49 | 50 | def process(filename): 51 | with open(filename, 'r') as f: 52 | map(process_line, f.readlines()) 53 | sys.stdout.flush() 54 | 55 | 56 | if __name__ == '__main__': 57 | process('ipv6.h') 58 | -------------------------------------------------------------------------------- /test/check-wasm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Pre-test check: Verify WASM module is built before running tests 3 | */ 4 | 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | 8 | const wasmPath = path.join(__dirname, '../docs/ipv6-parse.js'); 9 | const apiPath = path.join(__dirname, '../docs/ipv6-parse-api.js'); 10 | 11 | console.log('Checking for WASM build artifacts...\n'); 12 | 13 | const missing = []; 14 | 15 | if (!fs.existsSync(wasmPath)) { 16 | missing.push('docs/ipv6-parse.js (WASM module)'); 17 | } 18 | 19 | if (!fs.existsSync(apiPath)) { 20 | missing.push('docs/ipv6-parse-api.js (API layer)'); 21 | } 22 | 23 | if (missing.length > 0) { 24 | console.error('❌ WASM build artifacts not found:\n'); 25 | missing.forEach(file => console.error(` - ${file}`)); 26 | console.error('\nPlease build the WASM module first:'); 27 | console.error(' npm run build'); 28 | console.error(' OR'); 29 | console.error(' ./build_wasm.sh\n'); 30 | process.exit(1); 31 | } 32 | 33 | // Check file sizes to ensure they're not empty 34 | const wasmStats = fs.statSync(wasmPath); 35 | const apiStats = fs.statSync(apiPath); 36 | 37 | if (wasmStats.size < 1000) { 38 | console.error('❌ WASM module file is too small (likely corrupt)'); 39 | console.error(` Size: ${wasmStats.size} bytes\n`); 40 | process.exit(1); 41 | } 42 | 43 | if (apiStats.size < 100) { 44 | console.error('❌ API layer file is too small (likely corrupt)'); 45 | console.error(` Size: ${apiStats.size} bytes\n`); 46 | process.exit(1); 47 | } 48 | 49 | console.log('✓ WASM module found:', wasmPath); 50 | console.log(` Size: ${(wasmStats.size / 1024).toFixed(2)} KB`); 51 | console.log('✓ API layer found:', apiPath); 52 | console.log(` Size: ${(apiStats.size / 1024).toFixed(2)} KB`); 53 | console.log(''); 54 | -------------------------------------------------------------------------------- /run_coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Script to generate code coverage reports for ipv6-parse 3 | 4 | set -e 5 | 6 | echo "=== IPv6 Parse Code Coverage Analysis ===" 7 | echo "" 8 | 9 | # Clean previous build and coverage data 10 | echo "Cleaning previous build and coverage data..." 11 | rm -rf build_coverage 12 | mkdir -p build_coverage 13 | cd build_coverage 14 | 15 | # Configure with coverage enabled 16 | echo "Configuring CMake with coverage enabled..." 17 | cmake -DENABLE_COVERAGE=1 -DIPV6_PARSE_LIBRARY_ONLY=OFF .. 18 | 19 | # Build the project 20 | echo "Building project..." 21 | make -j4 22 | 23 | # Run tests to generate coverage data 24 | echo "" 25 | echo "Running tests to generate coverage data..." 26 | ./bin/ipv6-test 27 | ./bin/ipv6-fuzz 28 | 29 | # Generate coverage reports 30 | echo "" 31 | echo "Generating coverage report..." 32 | cd .. 33 | 34 | # Check if lcov is available 35 | if command -v lcov &> /dev/null; then 36 | echo "Using lcov to generate HTML report..." 37 | lcov --capture --directory build_coverage --output-file build_coverage/coverage.info 38 | lcov --remove build_coverage/coverage.info '/usr/*' --output-file build_coverage/coverage.info 39 | lcov --list build_coverage/coverage.info 40 | 41 | if command -v genhtml &> /dev/null; then 42 | genhtml build_coverage/coverage.info --output-directory build_coverage/coverage_html 43 | echo "" 44 | echo "HTML coverage report generated in: build_coverage/coverage_html/index.html" 45 | fi 46 | else 47 | echo "lcov not found. Using gcov directly..." 48 | cd build_coverage 49 | gcov -r ../ipv6.c 50 | echo "" 51 | echo "Coverage files generated: *.gcov" 52 | cd .. 53 | fi 54 | 55 | echo "" 56 | echo "=== Coverage Analysis Complete ===" 57 | echo "To view text summary, check the output above" 58 | echo "To view detailed HTML report: open build_coverage/coverage_html/index.html" 59 | -------------------------------------------------------------------------------- /ipv6-parse.spec: -------------------------------------------------------------------------------- 1 | Name: ipv6-parse 2 | Version: 1.2.1 3 | Release: 1%{?dist} 4 | Summary: IPv6/IPv4 address parser with full RFC compliance 5 | 6 | License: MIT 7 | URL: https://github.com/jrepp/ipv6-parse 8 | Source0: %{name}-%{version}.tar.gz 9 | 10 | BuildRequires: gcc 11 | BuildRequires: cmake >= 3.12 12 | BuildRequires: make 13 | 14 | %description 15 | High-performance IPv6/IPv4 address parser with full RFC compliance 16 | (RFC 4291, RFC 5952, RFC 4007). 17 | 18 | Features: 19 | - Full RFC compliance for IPv6 and IPv4 address parsing 20 | - Support for CIDR masks, ports, and zone IDs 21 | - IPv4-mapped and IPv4-compatible address handling 22 | - High-performance C implementation 23 | - Zero dependencies 24 | 25 | %package devel 26 | Summary: Development files for %{name} 27 | Requires: %{name}%{?_isa} = %{version}-%{release} 28 | 29 | %description devel 30 | The %{name}-devel package contains libraries and header files for 31 | developing applications that use %{name}. 32 | 33 | %prep 34 | %autosetup 35 | 36 | %build 37 | %cmake -DBUILD_SHARED_LIBS=ON 38 | %cmake_build 39 | 40 | %install 41 | %cmake_install 42 | 43 | %check 44 | %ctest 45 | 46 | %files 47 | %license LICENSE 48 | %doc README.md 49 | %{_libdir}/libipv6-parse.so.1 50 | %{_libdir}/libipv6-parse.so.%{version} 51 | 52 | %files devel 53 | %{_includedir}/ipv6.h 54 | %{_includedir}/ipv6_config.h 55 | %{_libdir}/libipv6-parse.so 56 | %{_libdir}/pkgconfig/ipv6-parse.pc 57 | %{_libdir}/cmake/ipv6-parse/ 58 | 59 | %changelog 60 | * Wed Nov 12 2025 Jacob Repp - 1.2.1-1 61 | - Initial RPM package release 62 | - Full RFC compliance (RFC 4291, RFC 5952, RFC 4007) 63 | - Support for CIDR masks, ports, and zone IDs 64 | - IPv4-mapped and IPv4-compatible address handling 65 | - High-performance C implementation 66 | - Shared and static library support 67 | - pkg-config integration 68 | - CMake package configuration 69 | -------------------------------------------------------------------------------- /cmake/emscripten.cmake: -------------------------------------------------------------------------------- 1 | # Emscripten toolchain file for building WebAssembly target 2 | # Usage: cmake -DCMAKE_TOOLCHAIN_FILE=cmake/emscripten.cmake -S . -B build_wasm 3 | 4 | # Check if EMSCRIPTEN environment variable is set 5 | if(NOT DEFINED ENV{EMSDK}) 6 | message(FATAL_ERROR 7 | "EMSDK environment variable not set. Please install and activate Emscripten SDK:\n" 8 | " git clone https://github.com/emscripten-core/emsdk.git\n" 9 | " cd emsdk\n" 10 | " ./emsdk install latest\n" 11 | " ./emsdk activate latest\n" 12 | " source ./emsdk_env.sh") 13 | endif() 14 | 15 | # Set the Emscripten root path 16 | set(EMSCRIPTEN_ROOT_PATH "$ENV{EMSDK}/upstream/emscripten") 17 | 18 | if(NOT EXISTS ${EMSCRIPTEN_ROOT_PATH}) 19 | message(FATAL_ERROR "Emscripten not found at ${EMSCRIPTEN_ROOT_PATH}") 20 | endif() 21 | 22 | # Set CMake system and compilers 23 | set(CMAKE_SYSTEM_NAME Emscripten) 24 | set(CMAKE_SYSTEM_VERSION 1) 25 | set(CMAKE_CROSSCOMPILING TRUE) 26 | 27 | # Set the compilers 28 | set(CMAKE_C_COMPILER "${EMSCRIPTEN_ROOT_PATH}/emcc") 29 | set(CMAKE_CXX_COMPILER "${EMSCRIPTEN_ROOT_PATH}/em++") 30 | set(CMAKE_AR "${EMSCRIPTEN_ROOT_PATH}/emar" CACHE FILEPATH "Emscripten ar") 31 | set(CMAKE_RANLIB "${EMSCRIPTEN_ROOT_PATH}/emranlib" CACHE FILEPATH "Emscripten ranlib") 32 | 33 | # Set the find root path 34 | set(CMAKE_FIND_ROOT_PATH ${EMSCRIPTEN_ROOT_PATH}) 35 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 36 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 37 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 38 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) 39 | 40 | # Emscripten-specific flags 41 | set(CMAKE_C_FLAGS_INIT "-s WASM=1") 42 | set(CMAKE_CXX_FLAGS_INIT "-s WASM=1") 43 | 44 | # Set executable suffix 45 | set(CMAKE_EXECUTABLE_SUFFIX ".js") 46 | 47 | message(STATUS "Emscripten toolchain configured") 48 | message(STATUS " EMSCRIPTEN_ROOT_PATH: ${EMSCRIPTEN_ROOT_PATH}") 49 | message(STATUS " CMAKE_C_COMPILER: ${CMAKE_C_COMPILER}") 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ipv6-parse", 3 | "version": "1.2.1", 4 | "description": "High-performance IPv6/IPv4 address parser with full RFC compliance (RFC 4291, RFC 5952, RFC 4007) compiled to WebAssembly", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "files": [ 8 | "index.js", 9 | "index.d.ts", 10 | "docs/ipv6-parse.js", 11 | "docs/ipv6-parse-api.js", 12 | "docs/ipv6-parse-api.d.ts", 13 | "README.md", 14 | "README_WASM.md", 15 | "LICENSE" 16 | ], 17 | "scripts": { 18 | "test": "npm run lint && npm run test:node && npm run test:wasm && npm run test:sync && npm run test:errors && npm run test:diagnostics", 19 | "test:node": "node test/test-node.js", 20 | "test:wasm": "node test/test-wasm.js", 21 | "test:api": "node test/test-api.js", 22 | "test:sync": "node test/test-sync-api.js", 23 | "test:errors": "node test/test-errors.js", 24 | "test:diagnostics": "node test/test-diagnostics.js", 25 | "test:performance": "node test/test-performance.js", 26 | "bench": "node test/test-performance.js", 27 | "lint": "eslint index.js docs/ipv6-parse-api.js test/*.js", 28 | "lint:fix": "eslint --fix index.js docs/ipv6-parse-api.js test/*.js", 29 | "build": "./build_wasm.sh", 30 | "validate": "node test/validate-build.js", 31 | "pretest": "node test/check-wasm.js", 32 | "prepublishOnly": "npm run build && npm run test" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "git+https://github.com/jrepp/ipv6-parse.git" 37 | }, 38 | "keywords": [ 39 | "ipv6", 40 | "ipv4", 41 | "parser", 42 | "address", 43 | "rfc4291", 44 | "rfc5952", 45 | "rfc4007", 46 | "wasm", 47 | "webassembly", 48 | "cidr", 49 | "port", 50 | "zone-id", 51 | "network" 52 | ], 53 | "author": "Jacob Repp ", 54 | "license": "MIT", 55 | "bugs": { 56 | "url": "https://github.com/jrepp/ipv6-parse/issues" 57 | }, 58 | "homepage": "https://github.com/jrepp/ipv6-parse#readme", 59 | "engines": { 60 | "node": ">=12.0.0" 61 | }, 62 | "devDependencies": { 63 | "eslint": "^8.57.0" 64 | }, 65 | "dependencies": {} 66 | } 67 | -------------------------------------------------------------------------------- /test_all.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-2025 Jacob Repp 3 | * 4 | * SPDX-License-Identifier: MIT 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include "ipv6.h" 26 | #include "ipv6_config.h" 27 | #include "ipv6_test_config.h" 28 | 29 | #ifdef HAVE_STDIO_H 30 | #include 31 | #endif 32 | 33 | // Declare the main functions from each test suite 34 | extern int test_main(void); // from test.c - we'll rename main 35 | extern int test_extended_main(void); // from test_extended.c - we'll rename main 36 | 37 | int main(void) { 38 | int result = 0; 39 | 40 | printf("=================\n"); 41 | printf("Running Original Test Suite\n"); 42 | printf("=================\n\n"); 43 | result |= test_main(); 44 | 45 | printf("\n\n"); 46 | printf("=================\n"); 47 | printf("Running Extended Test Suite\n"); 48 | printf("=================\n\n"); 49 | result |= test_extended_main(); 50 | 51 | printf("\n\n"); 52 | printf("=================\n"); 53 | printf("All Tests Complete\n"); 54 | printf("=================\n"); 55 | 56 | return result; 57 | } 58 | -------------------------------------------------------------------------------- /coverage_analysis.md: -------------------------------------------------------------------------------- 1 | # IPv6-Parse Test Coverage Analysis 2 | 3 | ## Current Coverage Status 4 | - **Lines executed:** 89.45% of 455 lines 5 | - **Branches executed:** 98.83% of 256 branches 6 | - **Branches taken at least once:** 80.08% of 256 branches 7 | 8 | ## Uncovered Areas Identified 9 | 10 | ### 1. Uppercase Hexadecimal Digits (ipv6.c:271-273) 11 | - **Location:** `read_hexidecimal_token()` function 12 | - **Missing:** Handling of uppercase hex digits 'A'-'F' 13 | - **Test needed:** Addresses with uppercase hex like "FFFF::1" or "2001:DB8::1" 14 | 15 | ### 2. Interface/Zone ID Support (ipv6.c:495-498, 567-580) 16 | - **Location:** STATE_IFACE handling in state machine 17 | - **Missing:** Zone ID parsing (e.g., "fe80::1%eth0") 18 | - **Test needed:** Addresses with zone IDs like "fe80::1%eth0", "[fe80::1%lo0]:8080" 19 | 20 | ### 3. CIDR Mask in STATE_NONE (ipv6.c:421-423) 21 | - **Location:** Direct CIDR without leading address component 22 | - **Missing:** Edge case where CIDR appears immediately 23 | - **Test needed:** Malformed addresses starting with '/' 24 | 25 | ### 4. IPv4 Embedding Error Paths (ipv6.c:821-823) 26 | - **Location:** Validation of IPv4 octet count in embedding 27 | - **Missing:** Test for incomplete IPv4 embedding (< 4 octets) 28 | - **Test needed:** Addresses like "::ffff:1.2.3" (only 3 octets) 29 | 30 | ### 5. String Size Exceeded (ipv6.c:723-725) 31 | - **Location:** Input validation for oversized strings 32 | - **Missing:** Test with input > IPV6_STRING_SIZE (66 chars) 33 | - **Test needed:** Very long address strings 34 | 35 | ### 6. Token Validation Edge Cases (ipv6.c:276-277) 36 | - **Location:** Invalid characters in hex tokens 37 | - **Missing:** Error handling for invalid hex in token 38 | - **Test needed:** Addresses with invalid characters after validation 39 | 40 | ### 7. Zero-run Move Count Validation (ipv6.c:854, 858) 41 | - **Location:** Zero-run expansion validation 42 | - **Missing:** Invalid move_count and target validation 43 | - **Test needed:** Edge cases in :: expansion logic 44 | 45 | ### 8. Whitespace in STATE_POST_ADDR (ipv6.c:783-784) 46 | - **Location:** Whitespace handling after ']' 47 | - **Missing:** Addresses with trailing whitespace after brackets 48 | - **Test needed:** "[::1]:8080 " (with trailing spaces) 49 | 50 | ### 9. Invalid Input After Various States (ipv6.c:605, 562, 507) 51 | - **Location:** Invalid transitions in state machine 52 | - **Missing:** Various invalid character sequences 53 | - **Test needed:** Malformed addresses triggering these paths 54 | 55 | ## Recommended Additional Tests 56 | 57 | ### High Priority 58 | 1. Uppercase hex digits: "FFFF::ABCD", "2001:DB8::1" 59 | 2. Zone IDs: "fe80::1%eth0", "[fe80::1%lo0]:8080" 60 | 3. Oversized input: Strings > 66 characters 61 | 4. Incomplete IPv4 embed: "::ffff:1.2.3", "::1.2" 62 | 63 | ### Medium Priority 64 | 5. Trailing whitespace: "[::1]:8080 ", "::1 " 65 | 6. Invalid state transitions 66 | 7. Edge cases in zero-run expansion 67 | 68 | ### Low Priority (Error paths) 69 | 8. Various malformed inputs to cover remaining error handling branches 70 | -------------------------------------------------------------------------------- /conanfile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from conan import ConanFile 5 | from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout 6 | from conan.tools.files import copy, load 7 | import os 8 | import re 9 | 10 | 11 | class Ipv6ParseConan(ConanFile): 12 | name = "ipv6-parse" 13 | description = "Fast, RFC-compliant IPv6/IPv4 address parser with CIDR, port, and zone ID support" 14 | topics = ("ipv6", "ipv4", "parser", "address", "cidr", "rfc", "networking") 15 | homepage = "https://github.com/jrepp/ipv6-parse" 16 | url = "https://github.com/jrepp/ipv6-parse" 17 | license = "MIT" 18 | 19 | # Binary configuration 20 | settings = "os", "compiler", "build_type", "arch" 21 | options = { 22 | "shared": [True, False], 23 | "fPIC": [True, False], 24 | } 25 | default_options = { 26 | "shared": False, 27 | "fPIC": True, 28 | } 29 | 30 | exports_sources = ( 31 | "CMakeLists.txt", 32 | "cmake/*", 33 | "ipv6.c", 34 | "ipv6.h", 35 | "ipv6_config.h.in", 36 | "ipv6-parse.pc.in", 37 | "LICENSE", 38 | "README.md", # Required by CPack in CMakeLists.txt 39 | ) 40 | 41 | def set_version(self): 42 | """Extract version from CMakeLists.txt""" 43 | content = load(self, os.path.join(self.recipe_folder, "CMakeLists.txt")) 44 | # CMakeLists.txt uses project(ipv6 VERSION x.y.z) 45 | version_match = re.search(r"project\(ipv6 VERSION \"?([0-9.]+)\"?", content) 46 | if version_match: 47 | self.version = version_match.group(1) 48 | else: 49 | self.version = "1.2.1" 50 | 51 | def config_options(self): 52 | if self.settings.os == "Windows": 53 | del self.options.fPIC 54 | 55 | def configure(self): 56 | if self.options.shared: 57 | self.options.rm_safe("fPIC") 58 | # Pure C library 59 | self.settings.rm_safe("compiler.libcxx") 60 | self.settings.rm_safe("compiler.cppstd") 61 | 62 | def layout(self): 63 | cmake_layout(self, src_folder=".") 64 | 65 | def generate(self): 66 | tc = CMakeToolchain(self) 67 | tc.variables["IPV6_PARSE_LIBRARY_ONLY"] = True 68 | tc.variables["BUILD_SHARED_LIBS"] = self.options.shared 69 | tc.variables["ENABLE_COVERAGE"] = False 70 | tc.variables["PARSE_TRACE"] = False 71 | # Let Conan control MSVC runtime settings 72 | tc.variables["IPV6_CONAN_BUILD"] = True 73 | tc.generate() 74 | 75 | def build(self): 76 | cmake = CMake(self) 77 | cmake.configure() 78 | cmake.build() 79 | 80 | def package(self): 81 | copy(self, "LICENSE", 82 | src=self.source_folder, 83 | dst=os.path.join(self.package_folder, "licenses")) 84 | cmake = CMake(self) 85 | cmake.install() 86 | 87 | def package_info(self): 88 | self.cpp_info.libs = ["ipv6-parse"] 89 | self.cpp_info.includedirs = ["include"] 90 | 91 | # Set properties for pkg-config 92 | self.cpp_info.set_property("pkg_config_name", "ipv6-parse") 93 | 94 | # Set properties for CMake find_package 95 | self.cpp_info.set_property("cmake_file_name", "ipv6-parse") 96 | self.cpp_info.set_property("cmake_target_name", "ipv6-parse::ipv6-parse") 97 | -------------------------------------------------------------------------------- /cmdline.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-2025 Jacob Repp 3 | * 4 | * SPDX-License-Identifier: MIT 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include "ipv6.h" 26 | #include "ipv6_config.h" 27 | 28 | #ifdef WIN32 29 | #pragma warning(disable:4820) // padding warnings 30 | #endif 31 | 32 | #ifdef HAVE_STDIO_H 33 | #include 34 | #endif 35 | 36 | #ifdef HAVE_STRING_H 37 | #include 38 | #endif 39 | 40 | #ifdef HAVE_MALLOC_H 41 | #include 42 | #endif 43 | 44 | #ifdef HAVE_ALLOCA_H 45 | #include 46 | #endif 47 | 48 | typedef struct { 49 | const char* message; 50 | ipv6_diag_event_t event; 51 | size_t calls; 52 | } diag_capture_t; 53 | 54 | 55 | // Function that is called by the address parser to report diagnostics 56 | static void cmdline_parsing_diag_fn ( 57 | ipv6_diag_event_t event, 58 | const ipv6_diag_info_t* info, 59 | void* user_data) 60 | { 61 | (void)user_data; 62 | printf("error: %s, event-code: (%u)\n", info->message, event); 63 | printf(" %s\n", info->input); 64 | printf(" %*s\n", info->position, info->input); 65 | } 66 | 67 | 68 | int main (int argc, const char** argv) { 69 | if (argc < 2) { 70 | printf("usage: %s
\n", argv[0]); 71 | return 1; 72 | } 73 | 74 | { 75 | ipv6_address_full_t addr, addr2; 76 | const char* str = argv[1]; 77 | if (!ipv6_from_str_diag(str, strlen(str), &addr, cmdline_parsing_diag_fn, NULL)) { 78 | printf("- failed to parse: '%s'\n", str); 79 | return 2; 80 | } 81 | 82 | char* buffer = (char*)alloca(IPV6_STRING_SIZE); 83 | if (!ipv6_to_str(&addr, buffer, sizeof(char) * IPV6_STRING_SIZE)) { 84 | printf("- failed to convert: '%s'\n", str); 85 | return 3; 86 | } 87 | 88 | if (!ipv6_from_str_diag(buffer, strlen(buffer), &addr2, cmdline_parsing_diag_fn, NULL)) { 89 | printf("- failed to roundtrip: '%s'\n", buffer); 90 | return 4; 91 | } 92 | 93 | // Allow embedded and compatible addresses to compare as equal 94 | if (IPV6_COMPARE_OK != ipv6_compare(&addr, &addr2, IPV6_FLAG_IPV4_COMPAT)) { 95 | printf("- failed to compare: '%s' != '%s'\n", str, buffer); 96 | return 5; 97 | } 98 | 99 | printf("OK (%s)\n", buffer); 100 | } 101 | 102 | return 0; 103 | } 104 | 105 | 106 | -------------------------------------------------------------------------------- /README_CONAN.md: -------------------------------------------------------------------------------- 1 | # Conan Package for ipv6-parse 2 | 3 | This document describes how to use the ipv6-parse library with [Conan](https://conan.io/), the C/C++ package manager. 4 | 5 | ## Quick Start 6 | 7 | ### Installing from Local Source 8 | 9 | ```bash 10 | # Build and install to local cache 11 | conan create . --build=missing 12 | 13 | # Or export without building 14 | conan export . 15 | ``` 16 | 17 | ### Using in Your Project 18 | 19 | Add ipv6-parse to your `conanfile.txt`: 20 | 21 | ```ini 22 | [requires] 23 | ipv6-parse/1.2.1 24 | 25 | [generators] 26 | CMakeDeps 27 | CMakeToolchain 28 | ``` 29 | 30 | Or in your `conanfile.py`: 31 | 32 | ```python 33 | from conan import ConanFile 34 | 35 | class MyProjectConan(ConanFile): 36 | requires = "ipv6-parse/1.2.1" 37 | generators = "CMakeDeps", "CMakeToolchain" 38 | ``` 39 | 40 | ### CMake Integration 41 | 42 | In your `CMakeLists.txt`: 43 | 44 | ```cmake 45 | cmake_minimum_required(VERSION 3.12) 46 | project(myproject C) 47 | 48 | find_package(ipv6-parse REQUIRED) 49 | 50 | add_executable(myapp main.c) 51 | target_link_libraries(myapp ipv6-parse::ipv6-parse) 52 | ``` 53 | 54 | ### Building Your Project 55 | 56 | ```bash 57 | # Install dependencies 58 | conan install . --output-folder=build --build=missing 59 | 60 | # Configure with CMake 61 | cmake -B build -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake 62 | 63 | # Build 64 | cmake --build build 65 | ``` 66 | 67 | ## Package Options 68 | 69 | | Option | Default | Description | 70 | |----------|---------|-----------------------------------| 71 | | `shared` | `False` | Build shared library | 72 | | `fPIC` | `True` | Generate position-independent code | 73 | 74 | ### Example: Building Shared Library 75 | 76 | ```bash 77 | conan create . -o ipv6-parse/*:shared=True --build=missing 78 | ``` 79 | 80 | ## Example Usage 81 | 82 | ```c 83 | #include 84 | #include 85 | #include 86 | 87 | int main(void) { 88 | ipv6_address_full_t addr; 89 | const char *input = "2001:db8::1"; 90 | 91 | if (ipv6_from_str(input, strlen(input), &addr)) { 92 | char buffer[IPV6_STRING_SIZE]; 93 | ipv6_to_str(&addr, buffer, sizeof(buffer)); 94 | printf("Parsed: %s\n", buffer); 95 | } 96 | return 0; 97 | } 98 | ``` 99 | 100 | ## Development 101 | 102 | ### Running Tests 103 | 104 | The package includes a test consumer in `test_package/` that verifies the package works correctly: 105 | 106 | ```bash 107 | conan create . --build=missing 108 | ``` 109 | 110 | ### Supported Platforms 111 | 112 | - Linux (GCC, Clang) 113 | - macOS (Apple Clang) 114 | - Windows (MSVC) 115 | 116 | ### Conan 2.x Compatibility 117 | 118 | This package is designed for Conan 2.x and uses the modern Conan API. 119 | 120 | ## CI/CD Integration 121 | 122 | The Conan package is automatically tested and published via GitHub Actions: 123 | 124 | - **CI Testing**: Every push and PR runs `conan create` on Linux, macOS, and Windows 125 | - **Release Publishing**: Tagged releases (e.g., `v1.2.1`) trigger automatic Conan package creation 126 | 127 | ### Publishing to a Custom Remote 128 | 129 | To publish releases to your own Conan remote, configure these GitHub repository settings: 130 | 131 | 1. **Variables** (Settings > Secrets and variables > Actions > Variables): 132 | - `CONAN_REMOTE_URL`: Your Conan remote URL (e.g., `https://your-artifactory.com/artifactory/api/conan/conan-local`) 133 | 134 | 2. **Secrets** (Settings > Secrets and variables > Actions > Secrets): 135 | - `CONAN_API_KEY`: API key or token for authentication 136 | 137 | The publish step will be skipped if `CONAN_REMOTE_URL` is not configured. 138 | -------------------------------------------------------------------------------- /Formula/ipv6-parse.rb: -------------------------------------------------------------------------------- 1 | # Homebrew formula for ipv6-parse 2 | # To install: brew install --HEAD https://raw.githubusercontent.com/jrepp/ipv6-parse/master/Formula/ipv6-parse.rb 3 | # Or after publishing to homebrew-core: brew install ipv6-parse 4 | 5 | class Ipv6Parse < Formula 6 | desc "Fast, RFC-compliant IPv6/IPv4 address parser with CIDR, port, and zone ID support" 7 | homepage "https://github.com/jrepp/ipv6-parse" 8 | url "https://github.com/jrepp/ipv6-parse/archive/v1.2.1.tar.gz" 9 | sha256 "" # Will be computed on release 10 | license "MIT" 11 | head "https://github.com/jrepp/ipv6-parse.git", branch: "master" 12 | 13 | depends_on "cmake" => :build 14 | depends_on "emscripten" => [:build, :optional] 15 | 16 | def install 17 | # Build static and shared libraries 18 | system "cmake", "-S", ".", "-B", "build", 19 | "-DCMAKE_BUILD_TYPE=Release", 20 | "-DBUILD_SHARED_LIBS=ON", 21 | "-DCMAKE_INSTALL_PREFIX=#{prefix}", 22 | *std_cmake_args 23 | system "cmake", "--build", "build" 24 | system "cmake", "--install", "build" 25 | 26 | # Build WASM if emscripten is available 27 | if build.with? "emscripten" 28 | system "./build_wasm.sh" 29 | # Install WASM files to share/ipv6-parse/wasm 30 | (share/"ipv6-parse/wasm").install "docs/ipv6-parse.js" 31 | (share/"ipv6-parse/wasm").install "docs/ipv6-parse-api.js" 32 | (share/"ipv6-parse/wasm").install "docs/ipv6-parse-api.d.ts" 33 | (share/"ipv6-parse/wasm").install "docs/index.html" 34 | end 35 | 36 | # Install additional documentation 37 | doc.install "README.md", "README_WASM.md", "README_NPM.md" 38 | end 39 | 40 | test do 41 | # Test the command-line tool 42 | output = shell_output("#{bin}/ipv6-parse-cli '2001:db8::1'") 43 | assert_match "2001:db8::1", output 44 | assert_match "Parsed successfully", output 45 | 46 | # Test with CIDR notation 47 | output = shell_output("#{bin}/ipv6-parse-cli '2001:db8::/32'") 48 | assert_match "2001:db8::", output 49 | assert_match "Mask: 32", output 50 | 51 | # Test with port 52 | output = shell_output("#{bin}/ipv6-parse-cli '[::1]:8080'") 53 | assert_match "::1", output 54 | assert_match "Port: 8080", output 55 | 56 | # Test with zone ID 57 | output = shell_output("#{bin}/ipv6-parse-cli 'fe80::1%eth0'") 58 | assert_match "fe80::1", output 59 | assert_match "Zone: eth0", output 60 | 61 | # Test invalid address 62 | assert_match "Invalid IPv6 address", 63 | shell_output("#{bin}/ipv6-parse-cli 'not-an-address' 2>&1", 1) 64 | 65 | # Test C library integration 66 | (testpath/"test.c").write <<~EOS 67 | #include 68 | #include 69 | #include 70 | 71 | int main() { 72 | ipv6_address addr; 73 | if (ipv6_parse("2001:db8::1", &addr) == 0) { 74 | char formatted[IPV6_STRING_SIZE]; 75 | ipv6_format(&addr, formatted, sizeof(formatted)); 76 | if (strcmp(formatted, "2001:db8::1") == 0) { 77 | printf("Test passed\\n"); 78 | return 0; 79 | } 80 | } 81 | printf("Test failed\\n"); 82 | return 1; 83 | } 84 | EOS 85 | 86 | system ENV.cc, "test.c", "-I#{include}", "-L#{lib}", "-lipv6-parse", "-o", "test" 87 | assert_equal "Test passed\n", shell_output("./test") 88 | 89 | # Test pkg-config 90 | system "pkg-config", "--cflags", "--libs", "ipv6-parse" 91 | 92 | # Verify shared library is installed 93 | assert_predicate lib/"libipv6-parse.dylib", :exist? 94 | 95 | # Verify header is installed 96 | assert_predicate include/"ipv6.h", :exist? 97 | 98 | # Verify pkg-config file is installed 99 | assert_predicate lib/"pkgconfig/ipv6-parse.pc", :exist? 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /test/test-node.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Basic Node.js test for ipv6-parse NPM package 3 | */ 4 | 5 | const ipv6 = require('../index.js'); 6 | 7 | async function runTests() { 8 | console.log('IPv6-Parse Node.js Tests\n========================\n'); 9 | 10 | let passed = 0; 11 | let failed = 0; 12 | 13 | // Test 1: Basic IPv6 parsing 14 | try { 15 | const addr = await ipv6.parse('2001:db8::1'); 16 | console.assert(addr.formatted === '2001:db8::1', 'Test 1 failed: formatted address mismatch'); 17 | console.assert(addr.components[0] === 0x2001, 'Test 1 failed: component mismatch'); 18 | console.log('✓ Test 1: Basic IPv6 parsing'); 19 | passed++; 20 | } catch (err) { 21 | console.error('✗ Test 1 failed:', err.message); 22 | failed++; 23 | } 24 | 25 | // Test 2: IPv6 with port 26 | try { 27 | const addr = await ipv6.parse('[::1]:8080'); 28 | console.assert(addr.port === 8080, 'Test 2 failed: port mismatch'); 29 | console.assert(addr.hasPort === true, 'Test 2 failed: hasPort mismatch'); 30 | console.log('✓ Test 2: IPv6 with port'); 31 | passed++; 32 | } catch (err) { 33 | console.error('✗ Test 2 failed:', err.message); 34 | failed++; 35 | } 36 | 37 | // Test 3: IPv6 with CIDR 38 | try { 39 | const addr = await ipv6.parse('2001:db8::1/64'); 40 | console.assert(addr.mask === 64, 'Test 3 failed: mask mismatch'); 41 | console.assert(addr.hasMask === true, 'Test 3 failed: hasMask mismatch'); 42 | console.log('✓ Test 3: IPv6 with CIDR'); 43 | passed++; 44 | } catch (err) { 45 | console.error('✗ Test 3 failed:', err.message); 46 | failed++; 47 | } 48 | 49 | // Test 4: Validation 50 | try { 51 | const valid = await ipv6.isValid('2001:db8::1'); 52 | const invalid = await ipv6.isValid('invalid'); 53 | console.assert(valid === true, 'Test 4 failed: valid check failed'); 54 | console.assert(invalid === false, 'Test 4 failed: invalid check failed'); 55 | console.log('✓ Test 4: Validation'); 56 | passed++; 57 | } catch (err) { 58 | console.error('✗ Test 4 failed:', err.message); 59 | failed++; 60 | } 61 | 62 | // Test 5: Try parse (null on error) 63 | try { 64 | const result = await ipv6.tryParse('invalid'); 65 | console.assert(result === null, 'Test 5 failed: should return null'); 66 | console.log('✓ Test 5: Try parse with invalid input'); 67 | passed++; 68 | } catch (err) { 69 | console.error('✗ Test 5 failed:', err.message); 70 | failed++; 71 | } 72 | 73 | // Test 6: Comparison 74 | try { 75 | const equal = await ipv6.equals('::1', '0:0:0:0:0:0:0:1'); 76 | console.assert(equal === true, 'Test 6 failed: addresses should be equal'); 77 | console.log('✓ Test 6: Address comparison'); 78 | passed++; 79 | } catch (err) { 80 | console.error('✗ Test 6 failed:', err.message); 81 | failed++; 82 | } 83 | 84 | // Test 7: Comparison with options 85 | try { 86 | const equal = await ipv6.equals('[::1]:80', '[::1]:443', { ignorePort: true }); 87 | console.assert(equal === true, 'Test 7 failed: should be equal ignoring port'); 88 | console.log('✓ Test 7: Comparison with ignore options'); 89 | passed++; 90 | } catch (err) { 91 | console.error('✗ Test 7 failed:', err.message); 92 | failed++; 93 | } 94 | 95 | // Test 8: Version 96 | try { 97 | const version = await ipv6.getVersion(); 98 | console.assert(typeof version === 'string', 'Test 8 failed: version should be string'); 99 | console.assert(version.includes('wasm'), 'Test 8 failed: version should include "wasm"'); 100 | console.log(`✓ Test 8: Get version (${version})`); 101 | passed++; 102 | } catch (err) { 103 | console.error('✗ Test 8 failed:', err.message); 104 | failed++; 105 | } 106 | 107 | // Summary 108 | console.log('\n========================'); 109 | console.log(`Tests passed: ${passed}`); 110 | console.log(`Tests failed: ${failed}`); 111 | console.log('========================\n'); 112 | 113 | process.exit(failed > 0 ? 1 : 0); 114 | } 115 | 116 | runTests().catch(err => { 117 | console.error('Fatal error:', err); 118 | process.exit(1); 119 | }); 120 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Comprehensive test script for local development 3 | 4 | set -e 5 | 6 | # Colors for output 7 | RED='\033[0;31m' 8 | GREEN='\033[0;32m' 9 | YELLOW='\033[1;33m' 10 | BLUE='\033[0;34m' 11 | NC='\033[0m' # No Color 12 | 13 | echo -e "${BLUE}======================================${NC}" 14 | echo -e "${BLUE}IPv6-Parse Comprehensive Test Suite${NC}" 15 | echo -e "${BLUE}======================================${NC}\n" 16 | 17 | # Function to print section headers 18 | print_section() { 19 | echo -e "\n${BLUE}▶ $1${NC}" 20 | echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" 21 | } 22 | 23 | # Function to run command and check result 24 | run_test() { 25 | local name="$1" 26 | local cmd="$2" 27 | 28 | echo -e "${YELLOW}Running: ${name}${NC}" 29 | if eval "$cmd"; then 30 | echo -e "${GREEN}✓ ${name} passed${NC}" 31 | return 0 32 | else 33 | echo -e "${RED}✗ ${name} failed${NC}" 34 | return 1 35 | fi 36 | } 37 | 38 | # Track test results 39 | TESTS_PASSED=0 40 | TESTS_FAILED=0 41 | 42 | # 1. C Library Tests 43 | print_section "C Library Tests" 44 | 45 | if command -v cmake &> /dev/null; then 46 | if [ ! -d "build" ]; then 47 | echo "Creating build directory..." 48 | mkdir -p build 49 | cd build 50 | cmake .. -DENABLE_COVERAGE=OFF -DIPV6_PARSE_LIBRARY_ONLY=OFF 51 | cd .. 52 | fi 53 | 54 | if run_test "Build C library" "cmake --build build --config Release"; then 55 | ((TESTS_PASSED++)) 56 | else 57 | ((TESTS_FAILED++)) 58 | fi 59 | 60 | if run_test "C library tests" "cd build && ctest --output-on-failure -C Release"; then 61 | ((TESTS_PASSED++)) 62 | else 63 | ((TESTS_FAILED++)) 64 | fi 65 | else 66 | echo -e "${YELLOW}⚠ CMake not found, skipping C library tests${NC}" 67 | fi 68 | 69 | # 2. WebAssembly Build 70 | print_section "WebAssembly Build" 71 | 72 | if [ -n "$EMSDK" ]; then 73 | if run_test "Build WASM module" "./build_wasm.sh"; then 74 | ((TESTS_PASSED++)) 75 | else 76 | ((TESTS_FAILED++)) 77 | fi 78 | else 79 | echo -e "${YELLOW}⚠ Emscripten not found, skipping WASM build${NC}" 80 | echo " Set up Emscripten: source /path/to/emsdk/emsdk_env.sh" 81 | 82 | # Check if WASM already exists 83 | if [ -f "docs/ipv6-parse.js" ]; then 84 | echo -e "${GREEN} ✓ Using existing WASM build${NC}" 85 | else 86 | echo -e "${RED} ✗ WASM module not found, some tests will fail${NC}" 87 | fi 88 | fi 89 | 90 | # 3. Node.js Setup 91 | print_section "Node.js Setup" 92 | 93 | if command -v node &> /dev/null; then 94 | echo "Node.js version: $(node --version)" 95 | echo "npm version: $(npm --version)" 96 | 97 | if [ ! -d "node_modules" ]; then 98 | if run_test "Install npm dependencies" "npm install"; then 99 | ((TESTS_PASSED++)) 100 | else 101 | ((TESTS_FAILED++)) 102 | fi 103 | else 104 | echo -e "${GREEN}✓ npm dependencies already installed${NC}" 105 | fi 106 | else 107 | echo -e "${RED}✗ Node.js not found, cannot run JavaScript tests${NC}" 108 | exit 1 109 | fi 110 | 111 | # 4. Linting 112 | print_section "Code Linting (ESLint)" 113 | 114 | if run_test "ESLint" "npm run lint"; then 115 | ((TESTS_PASSED++)) 116 | else 117 | ((TESTS_FAILED++)) 118 | echo -e "${YELLOW} Tip: Run 'npm run lint:fix' to auto-fix issues${NC}" 119 | fi 120 | 121 | # 5. Build Validation 122 | print_section "Build Output Validation" 123 | 124 | if run_test "Validate build output" "npm run validate"; then 125 | ((TESTS_PASSED++)) 126 | else 127 | ((TESTS_FAILED++)) 128 | fi 129 | 130 | # 6. Node.js Tests 131 | print_section "Node.js Tests" 132 | 133 | if run_test "Node.js wrapper tests" "npm run test:node"; then 134 | ((TESTS_PASSED++)) 135 | else 136 | ((TESTS_FAILED++)) 137 | fi 138 | 139 | # 7. WASM Tests 140 | print_section "WebAssembly Module Tests" 141 | 142 | if run_test "WASM module tests" "npm run test:wasm"; then 143 | ((TESTS_PASSED++)) 144 | else 145 | ((TESTS_FAILED++)) 146 | fi 147 | 148 | # 8. API Tests 149 | print_section "JavaScript API Tests" 150 | 151 | if run_test "API layer tests" "npm run test:api"; then 152 | ((TESTS_PASSED++)) 153 | else 154 | ((TESTS_FAILED++)) 155 | fi 156 | 157 | # Summary 158 | print_section "Test Summary" 159 | 160 | echo "" 161 | echo -e "Tests passed: ${GREEN}${TESTS_PASSED}${NC}" 162 | echo -e "Tests failed: ${RED}${TESTS_FAILED}${NC}" 163 | echo "" 164 | 165 | if [ $TESTS_FAILED -eq 0 ]; then 166 | echo -e "${GREEN}═══════════════════════════════════${NC}" 167 | echo -e "${GREEN}✓ All tests passed successfully!${NC}" 168 | echo -e "${GREEN}═══════════════════════════════════${NC}\n" 169 | exit 0 170 | else 171 | echo -e "${RED}═══════════════════════════════════${NC}" 172 | echo -e "${RED}✗ Some tests failed${NC}" 173 | echo -e "${RED}═══════════════════════════════════${NC}\n" 174 | exit 1 175 | fi 176 | -------------------------------------------------------------------------------- /test_wasm_node.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Node.js Test for IPv6 Parser WASM Integration 5 | * 6 | * Tests that the WASM module loads correctly and memory is accessible. 7 | */ 8 | 9 | const path = require('path'); 10 | 11 | // Import the API wrapper 12 | const IPv6Parser = require('./docs/ipv6-parse-api.js').IPv6Parser; 13 | 14 | async function runTests() { 15 | console.log('=== IPv6 Parser WASM Integration Test ===\n'); 16 | 17 | try { 18 | // Step 1: Load WASM module 19 | console.log('Step 1: Loading WASM module...'); 20 | const createIPv6Module = require('./docs/ipv6-parse.js'); 21 | const wasmModule = await createIPv6Module(); 22 | console.log('✓ WASM module loaded\n'); 23 | 24 | // Step 2: Check module properties 25 | console.log('Step 2: Checking module properties...'); 26 | console.log(` - Has ccall: ${typeof wasmModule.ccall === 'function'}`); 27 | console.log(` - Has cwrap: ${typeof wasmModule.cwrap === 'function'}`); 28 | console.log(` - Has _malloc: ${typeof wasmModule._malloc === 'function'}`); 29 | console.log(` - Has _free: ${typeof wasmModule._free === 'function'}`); 30 | console.log(` - Has UTF8ToString: ${typeof wasmModule.UTF8ToString === 'function'}`); 31 | console.log(` - Has HEAPU8: ${typeof wasmModule.HEAPU8 !== 'undefined'}`); 32 | console.log(` - Has HEAPU16: ${typeof wasmModule.HEAPU16 !== 'undefined'}`); 33 | console.log(` - Has HEAPU32: ${typeof wasmModule.HEAPU32 !== 'undefined'}`); 34 | 35 | if (!wasmModule.HEAPU8) { 36 | console.error('\n✗ HEAPU8 not available!'); 37 | console.log(' Checking alternative memory access methods...'); 38 | console.log(` - wasmMemory: ${typeof wasmModule.wasmMemory}`); 39 | console.log(` - buffer: ${typeof wasmModule.buffer}`); 40 | console.log(` - asm: ${typeof wasmModule.asm}`); 41 | 42 | // Try to manually create memory views 43 | console.log(' Attempting to find memory buffer...'); 44 | let buffer = null; 45 | if (wasmModule.wasmMemory && wasmModule.wasmMemory.buffer) { 46 | buffer = wasmModule.wasmMemory.buffer; 47 | console.log(' Found buffer at: wasmModule.wasmMemory.buffer'); 48 | } else if (wasmModule.buffer) { 49 | buffer = wasmModule.buffer; 50 | console.log(' Found buffer at: wasmModule.buffer'); 51 | } else { 52 | // List all properties of wasmModule 53 | console.log(' Available properties on wasmModule:'); 54 | Object.keys(wasmModule).forEach(key => { 55 | console.log(` - ${key}: ${typeof wasmModule[key]}`); 56 | }); 57 | } 58 | 59 | if (buffer) { 60 | console.log(' Creating memory views manually...'); 61 | wasmModule.HEAPU8 = new Uint8Array(buffer); 62 | wasmModule.HEAPU16 = new Uint16Array(buffer); 63 | wasmModule.HEAPU32 = new Uint32Array(buffer); 64 | console.log('✓ Memory views created manually\n'); 65 | } else { 66 | throw new Error('Could not access WASM memory buffer'); 67 | } 68 | } else { 69 | console.log('✓ All memory arrays present\n'); 70 | } 71 | 72 | // Step 3: Test version function 73 | console.log('Step 3: Testing version function...'); 74 | const parser = new IPv6Parser(wasmModule); 75 | const version = parser.getVersion(); 76 | console.log(`✓ Version: ${version}\n`); 77 | 78 | // Step 4: Test parsing 79 | console.log('Step 4: Testing address parsing...'); 80 | const testAddresses = [ 81 | '2001:db8::1', 82 | '::1', 83 | '::ffff:192.0.2.1', 84 | '2001:db8::1/64', 85 | '[2001:db8::1]:8080', 86 | 'fe80::1%eth0' 87 | ]; 88 | 89 | for (const addr of testAddresses) { 90 | try { 91 | const result = parser.parse(addr); 92 | console.log(`✓ Parsed: ${addr}`); 93 | console.log(` Formatted: ${result.formatted}`); 94 | console.log(` Components: [${result.components.map(c => '0x' + c.toString(16).padStart(4, '0')).join(', ')}]`); 95 | if (result.port !== null) console.log(` Port: ${result.port}`); 96 | if (result.mask !== null) console.log(` CIDR: /${result.mask}`); 97 | if (result.zone !== null) console.log(` Zone: ${result.zone}`); 98 | } catch (err) { 99 | console.error(`✗ Failed to parse: ${addr}`); 100 | console.error(` Error: ${err.message}`); 101 | throw err; 102 | } 103 | } 104 | 105 | console.log('\n=== ALL TESTS PASSED ==='); 106 | console.log('The IPv6 parser WASM integration is working correctly!\n'); 107 | 108 | // Cleanup 109 | parser.destroy(); 110 | process.exit(0); 111 | 112 | } catch (err) { 113 | console.error('\n=== TEST FAILED ==='); 114 | console.error(`Error: ${err.message}`); 115 | console.error(`Stack: ${err.stack}`); 116 | process.exit(1); 117 | } 118 | } 119 | 120 | // Run tests 121 | runTests(); 122 | -------------------------------------------------------------------------------- /test/test-wasm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WASM module validation tests 3 | * Tests the WASM module directly without the Node.js wrapper 4 | */ 5 | 6 | const path = require('path'); 7 | 8 | // Load WASM module 9 | const createIPv6Module = require(path.join(__dirname, '../docs/ipv6-parse.js')); 10 | 11 | async function runTests() { 12 | console.log('WASM Module Validation Tests\n=============================\n'); 13 | 14 | let passed = 0; 15 | let failed = 0; 16 | 17 | try { 18 | // Initialize WASM module 19 | console.log('Initializing WASM module...'); 20 | const Module = await createIPv6Module(); 21 | console.log('✓ WASM module initialized\n'); 22 | 23 | // Test 1: Check exported functions 24 | console.log('Test 1: Checking exported functions...'); 25 | const requiredFunctions = [ 26 | 'ccall', 27 | 'cwrap', 28 | 'UTF8ToString', 29 | '_malloc', 30 | '_free' 31 | ]; 32 | 33 | for (const func of requiredFunctions) { 34 | if (typeof Module[func] !== 'function') { 35 | console.error(`✗ Missing function: ${func}`); 36 | failed++; 37 | } 38 | } 39 | 40 | if (failed === 0) { 41 | console.log('✓ All required functions exported'); 42 | passed++; 43 | } 44 | 45 | // Test 2: Call ipv6_parse_full 46 | console.log('\nTest 2: Testing ipv6_parse_full...'); 47 | try { 48 | const resultSize = 80; // sizeof(ipv6_parse_result_t) 49 | const resultPtr = Module._malloc(resultSize); 50 | 51 | const success = Module.ccall( 52 | 'ipv6_parse_full', 53 | 'number', 54 | ['string', 'number'], 55 | ['2001:db8::1', resultPtr] 56 | ); 57 | 58 | if (success === 1) { 59 | console.log('✓ ipv6_parse_full succeeded'); 60 | passed++; 61 | } else { 62 | console.error('✗ ipv6_parse_full failed'); 63 | failed++; 64 | } 65 | 66 | Module._free(resultPtr); 67 | } catch (err) { 68 | console.error('✗ Error calling ipv6_parse_full:', err.message); 69 | failed++; 70 | } 71 | 72 | // Test 3: Call ipv6_compare_str 73 | console.log('\nTest 3: Testing ipv6_compare_str...'); 74 | try { 75 | const result = Module.ccall( 76 | 'ipv6_compare_str', 77 | 'number', 78 | ['string', 'string', 'number'], 79 | ['::1', '0:0:0:0:0:0:0:1', 0] 80 | ); 81 | 82 | if (result === 0) { 83 | console.log('✓ ipv6_compare_str succeeded'); 84 | passed++; 85 | } else { 86 | console.error('✗ ipv6_compare_str returned:', result); 87 | failed++; 88 | } 89 | } catch (err) { 90 | console.error('✗ Error calling ipv6_compare_str:', err.message); 91 | failed++; 92 | } 93 | 94 | // Test 4: Call ipv6_version 95 | console.log('\nTest 4: Testing ipv6_version...'); 96 | try { 97 | const version = Module.ccall('ipv6_version', 'string', [], []); 98 | 99 | if (version && version.includes('wasm')) { 100 | console.log(`✓ ipv6_version succeeded: ${version}`); 101 | passed++; 102 | } else { 103 | console.error('✗ ipv6_version returned unexpected value:', version); 104 | failed++; 105 | } 106 | } catch (err) { 107 | console.error('✗ Error calling ipv6_version:', err.message); 108 | failed++; 109 | } 110 | 111 | // Test 5: Call ipv6_result_size 112 | console.log('\nTest 5: Testing ipv6_result_size...'); 113 | try { 114 | const size = Module.ccall('ipv6_result_size', 'number', [], []); 115 | 116 | // Structure size should be 92 bytes (with padding): 117 | // - components: 16 bytes (8 * uint16_t) 118 | // - port: 2 bytes 119 | // - pad0: 2 bytes (alignment) 120 | // - mask: 4 bytes 121 | // - flags: 4 bytes 122 | // - formatted: 48 bytes 123 | // - zone: 16 bytes 124 | if (size === 92) { 125 | console.log(`✓ ipv6_result_size succeeded: ${size} bytes`); 126 | passed++; 127 | } else { 128 | console.error('✗ ipv6_result_size returned unexpected value:', size, '(expected 92)'); 129 | failed++; 130 | } 131 | } catch (err) { 132 | console.error('✗ Error calling ipv6_result_size:', err.message); 133 | failed++; 134 | } 135 | 136 | // Test 6: Parse multiple formats 137 | console.log('\nTest 6: Testing various address formats...'); 138 | const testAddresses = [ 139 | '::1', 140 | '2001:db8::1', 141 | '[::1]:8080', 142 | 'fe80::1%eth0', 143 | '2001:db8::1/64', 144 | '::ffff:192.0.2.1' 145 | ]; 146 | 147 | let formatTests = 0; 148 | for (const addr of testAddresses) { 149 | const resultPtr = Module._malloc(80); 150 | const success = Module.ccall( 151 | 'ipv6_parse_full', 152 | 'number', 153 | ['string', 'number'], 154 | [addr, resultPtr] 155 | ); 156 | 157 | if (success === 1) { 158 | formatTests++; 159 | } else { 160 | console.error(` ✗ Failed to parse: ${addr}`); 161 | } 162 | 163 | Module._free(resultPtr); 164 | } 165 | 166 | if (formatTests === testAddresses.length) { 167 | console.log(`✓ All ${testAddresses.length} address formats parsed successfully`); 168 | passed++; 169 | } else { 170 | console.error(`✗ Only ${formatTests}/${testAddresses.length} formats parsed`); 171 | failed++; 172 | } 173 | 174 | } catch (err) { 175 | console.error('Fatal error:', err); 176 | process.exit(1); 177 | } 178 | 179 | // Summary 180 | console.log('\n============================='); 181 | console.log(`Tests passed: ${passed}`); 182 | console.log(`Tests failed: ${failed}`); 183 | console.log('=============================\n'); 184 | 185 | process.exit(failed > 0 ? 1 : 0); 186 | } 187 | 188 | runTests().catch(err => { 189 | console.error('Fatal error:', err); 190 | process.exit(1); 191 | }); 192 | -------------------------------------------------------------------------------- /docs/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IPv6 Parser Test (No Cache) 6 | 20 | 21 | 22 |

IPv6 Parser Test (Cache-Busted)

23 | 24 |
25 | 26 | 27 |
28 | 29 |
30 |
31 | 32 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ipv6-parse - Node.js entry point 3 | * 4 | * This module provides a high-performance IPv6/IPv4 address parser using WebAssembly. 5 | * It supports RFC 4291, RFC 5952, and RFC 4007 with full compliance. 6 | */ 7 | 8 | const path = require('path'); 9 | const fs = require('fs'); 10 | 11 | // Load the WASM module 12 | const createIPv6Module = require('./docs/ipv6-parse.js'); 13 | 14 | // Import the API layer 15 | let IPv6Parser, IPv6Address, IPv6ParseError, ipv6; 16 | 17 | /** 18 | * Initialize the IPv6 parser 19 | * @returns {Promise} Promise that resolves to the parser API 20 | */ 21 | async function createParser() { 22 | // If already initialized, return cached API 23 | if (_api) { 24 | return _api; 25 | } 26 | 27 | // Load WASM module 28 | const wasmModule = await createIPv6Module(); 29 | 30 | // Load API layer (we need to evaluate it in the current context) 31 | const apiCode = fs.readFileSync(path.join(__dirname, 'docs/ipv6-parse-api.js'), 'utf8'); 32 | 33 | // Create a minimal browser-like environment for the API layer 34 | const context = { 35 | module: { exports: {} }, 36 | Module: wasmModule 37 | }; 38 | 39 | // Execute API layer code 40 | const Function = global.Function; 41 | const apiFunc = new Function('module', 'Module', apiCode + '\nreturn module.exports;'); 42 | const api = apiFunc(context.module, wasmModule); 43 | 44 | // Cache exports 45 | IPv6Parser = api.IPv6Parser; 46 | IPv6Address = api.IPv6Address; 47 | IPv6ParseError = api.IPv6ParseError; 48 | 49 | // Create and return parser instance 50 | const parser = new IPv6Parser(wasmModule); 51 | 52 | // Create functional API 53 | ipv6 = { 54 | parse: (addr) => parser.parse(addr), 55 | tryParse: (addr) => parser.tryParse(addr), 56 | isValid: (addr) => parser.isValid(addr), 57 | equals: (addr1, addr2, opts) => parser.equals(addr1, addr2, opts), 58 | getVersion: () => parser.getVersion() 59 | }; 60 | 61 | // Cache API for getParser() and getAPI() 62 | _api = { 63 | parser, 64 | IPv6Parser, 65 | IPv6Address, 66 | IPv6ParseError, 67 | ipv6 68 | }; 69 | 70 | return _api; 71 | } 72 | 73 | // Synchronous API (uses lazy initialization) 74 | let _initPromise = null; 75 | let _api = null; 76 | 77 | function ensureInitialized() { 78 | if (_initPromise === null) { 79 | _initPromise = createParser().then(api => { 80 | _api = api; 81 | return api; 82 | }); 83 | } 84 | return _initPromise; 85 | } 86 | 87 | /** 88 | * Parse an IPv6/IPv4 address (async) 89 | * @param {string} address - Address to parse 90 | * @returns {Promise} Parsed address object 91 | */ 92 | async function parse(address) { 93 | await ensureInitialized(); 94 | return _api.ipv6.parse(address); 95 | } 96 | 97 | /** 98 | * Try to parse an address, returning null on failure (async) 99 | * @param {string} address - Address to parse 100 | * @returns {Promise} Parsed address or null 101 | */ 102 | async function tryParse(address) { 103 | await ensureInitialized(); 104 | return _api.ipv6.tryParse(address); 105 | } 106 | 107 | /** 108 | * Check if an address is valid (async) 109 | * @param {string} address - Address to validate 110 | * @returns {Promise} True if valid 111 | */ 112 | async function isValid(address) { 113 | await ensureInitialized(); 114 | return _api.ipv6.isValid(address); 115 | } 116 | 117 | /** 118 | * Compare two addresses for equality (async) 119 | * @param {string} addr1 - First address 120 | * @param {string} addr2 - Second address 121 | * @param {Object} options - Comparison options 122 | * @returns {Promise} True if equal 123 | */ 124 | async function equals(addr1, addr2, options = {}) { 125 | await ensureInitialized(); 126 | return _api.ipv6.equals(addr1, addr2, options); 127 | } 128 | 129 | /** 130 | * Get library version (async) 131 | * @returns {Promise} Version string 132 | */ 133 | async function getVersion() { 134 | await ensureInitialized(); 135 | return _api.ipv6.getVersion(); 136 | } 137 | 138 | /** 139 | * Get a synchronous parser instance (requires prior initialization) 140 | * @returns {Object} Parser instance with synchronous methods 141 | * @throws {Error} If not initialized 142 | */ 143 | function getParser() { 144 | if (!_api) { 145 | throw new Error('Parser not initialized. Call createParser() first or use async API.'); 146 | } 147 | return _api.parser; 148 | } 149 | 150 | /** 151 | * Get the synchronous functional API (requires prior initialization) 152 | * @returns {Object} Functional API with synchronous methods 153 | * @throws {Error} If not initialized 154 | */ 155 | function getAPI() { 156 | if (!_api) { 157 | throw new Error('API not initialized. Call createParser() first or use async API.'); 158 | } 159 | return _api.ipv6; 160 | } 161 | 162 | // Export both async convenience API and sync explicit API 163 | module.exports = { 164 | // === Async Convenience API (auto-initializes on first use) === 165 | // Use these for simple scripts where convenience matters more than performance 166 | parse, // async - auto-initializes 167 | tryParse, // async - auto-initializes 168 | isValid, // async - auto-initializes 169 | equals, // async - auto-initializes 170 | getVersion, // async - auto-initializes 171 | 172 | // === Sync Explicit API (requires explicit initialization) === 173 | // Use these for performance-critical code or when you need sync operations 174 | createParser, // Initialize once, then use parser.parse() synchronously 175 | getParser, // Get sync parser instance (throws if not initialized) 176 | getAPI, // Get sync functional API (throws if not initialized) 177 | 178 | // === Classes (available after initialization) === 179 | get IPv6Parser() { return IPv6Parser; }, 180 | get IPv6Address() { return IPv6Address; }, 181 | get IPv6ParseError() { return IPv6ParseError; }, 182 | 183 | // === Internal (available after initialization) === 184 | get ipv6() { return ipv6; } 185 | }; 186 | -------------------------------------------------------------------------------- /README_COVERAGE.md: -------------------------------------------------------------------------------- 1 | # Code Coverage Guide for IPv6-Parse 2 | 3 | This guide explains how to run code coverage analysis on the IPv6-parse library. 4 | 5 | ## Quick Start 6 | 7 | ### Option 1: Manual Build (Recommended when CMake unavailable) 8 | 9 | ```bash 10 | ./build_and_test_coverage.sh 11 | ``` 12 | 13 | This will: 14 | 1. Clean previous coverage data 15 | 2. Generate config headers for macOS/Linux 16 | 3. Compile with coverage instrumentation 17 | 4. Run tests 18 | 5. Generate coverage report 19 | 20 | ### Option 2: CMake Build 21 | 22 | ```bash 23 | ./run_coverage.sh 24 | ``` 25 | 26 | This will: 27 | 1. Configure CMake with `ENABLE_COVERAGE=1` 28 | 2. Build the project 29 | 3. Run all tests 30 | 4. Generate HTML coverage report (if lcov/genhtml available) 31 | 32 | ## Requirements 33 | 34 | ### Minimum Requirements 35 | - GCC or Clang compiler 36 | - gcov (usually included with GCC) 37 | 38 | ### Optional (for HTML reports) 39 | - lcov - Coverage data processing 40 | - genhtml - HTML report generation 41 | 42 | Install on macOS: 43 | ```bash 44 | brew install lcov 45 | ``` 46 | 47 | Install on Ubuntu/Debian: 48 | ```bash 49 | sudo apt-get install lcov 50 | ``` 51 | 52 | ## Test Suites 53 | 54 | The project includes two test suites: 55 | 56 | ### 1. Original Test Suite (test.c) 57 | - 315 tests 58 | - Comprehensive testing of core functionality 59 | - IPv6/IPv4 parsing, comparison, conversion 60 | 61 | ### 2. Extended Test Suite (test_extended.c) 62 | - 36 tests 63 | - Targets previously uncovered code paths 64 | - Edge cases and error handling 65 | 66 | ## Manual Coverage Workflow 67 | 68 | ### Step 1: Compile with Coverage 69 | ```bash 70 | gcc -std=c99 -Wall -Wno-long-long -pedantic \ 71 | --coverage -O0 -g \ 72 | -o ipv6-test ipv6.c test.c 73 | ``` 74 | 75 | ### Step 2: Run Tests 76 | ```bash 77 | ./ipv6-test 78 | ``` 79 | 80 | This generates `.gcda` files with execution counts. 81 | 82 | ### Step 3: Generate Coverage Report 83 | ```bash 84 | gcov -b ipv6.c 85 | ``` 86 | 87 | This creates `ipv6.c.gcov` with line-by-line coverage. 88 | 89 | ### Step 4: View Results 90 | ```bash 91 | # Summary 92 | head -5 ipv6.c.gcov 93 | 94 | # Find uncovered lines (marked with #####) 95 | grep "^ #####:" ipv6.c.gcov | head -20 96 | 97 | # Find partially covered branches (marked with *, -, or numbers) 98 | grep -E "branch.*(never|taken 0%)" ipv6.c.gcov | head -20 99 | ``` 100 | 101 | ## Coverage Report Format 102 | 103 | The `.gcov` file format: 104 | 105 | ``` 106 | -: 0:Source:ipv6.c 107 | 3: 15: int x = 0; // Executed 3 times 108 | #####: 16: return -1; // Never executed 109 | branch 0 taken 75% // Branch taken 75% of the time 110 | branch 1 taken 25% (fallthrough) // Branch taken 25% of the time 111 | ``` 112 | 113 | - `-`: Line not executable (comments, declarations) 114 | - Number: Execution count 115 | - `#####`: Never executed 116 | - `branch N taken X%`: Branch coverage 117 | 118 | ## CMake Integration 119 | 120 | To enable coverage in your own CMake build: 121 | 122 | ```bash 123 | mkdir build 124 | cd build 125 | cmake -DENABLE_COVERAGE=1 -DIPV6_PARSE_LIBRARY_ONLY=OFF .. 126 | make 127 | ctest 128 | ``` 129 | 130 | Then generate reports: 131 | ```bash 132 | cd .. 133 | gcov -b build/CMakeFiles/ipv6-test.dir/ipv6.c.o 134 | ``` 135 | 136 | ## Coverage Targets 137 | 138 | Current coverage status: 139 | - **Line Coverage**: 89.45% (455 lines) 140 | - **Branch Coverage**: 98.83% executed, 80.08% taken 141 | - **Target**: > 90% line and branch coverage 142 | 143 | ## Interpreting Results 144 | 145 | ### Good Coverage 146 | - Core parsing functions: > 95% 147 | - Error handling: > 80% 148 | - Public API: 100% 149 | 150 | ### Expected Low Coverage Areas 151 | - Platform-specific code (#ifdef blocks) 152 | - Unreachable error conditions 153 | - Defensive error checks 154 | 155 | ### Investigating Low Coverage 156 | 157 | 1. **Find uncovered lines:** 158 | ```bash 159 | grep "^ #####:" ipv6.c.gcov 160 | ``` 161 | 162 | 2. **Check if line is testable:** 163 | - Error recovery paths? 164 | - Platform-specific? 165 | - Defensive check? 166 | 167 | 3. **Write test if needed:** 168 | - Add to test_extended.c 169 | - Verify coverage improves 170 | 171 | ## Common Issues 172 | 173 | ### Issue: "gcov: no such file or directory" 174 | 175 | The gcov files include the executable name. Try: 176 | ```bash 177 | gcov -b -ipv6.gcda 178 | # Example: gcov -b ipv6-test-ipv6.gcda 179 | ``` 180 | 181 | ### Issue: "cannot open source file" 182 | 183 | Make sure you run gcov from the directory containing the source files. 184 | 185 | ### Issue: Coverage data shows 0% 186 | 187 | Ensure: 188 | 1. Compiled with `--coverage` 189 | 2. Linked with `--coverage` 190 | 3. Tests actually ran (check return code) 191 | 4. `.gcda` files were created (ls *.gcda) 192 | 193 | ## Best Practices 194 | 195 | 1. **Clean between runs:** 196 | ```bash 197 | rm -f *.gcda *.gcno *.gcov 198 | ``` 199 | 200 | 2. **Test both suites:** 201 | ```bash 202 | ./ipv6-test && ./ipv6-test-extended 203 | # Then run gcov to see combined coverage 204 | ``` 205 | 206 | 3. **Focus on uncovered branches:** 207 | Branch coverage often reveals edge cases. 208 | 209 | 4. **Don't aim for 100%:** 210 | 90-95% is excellent for real-world code. 211 | 212 | ## Continuous Integration 213 | 214 | Example GitHub Actions workflow: 215 | 216 | ```yaml 217 | - name: Run tests with coverage 218 | run: | 219 | ./build_and_test_coverage.sh 220 | 221 | - name: Upload coverage 222 | uses: codecov/codecov-action@v3 223 | with: 224 | files: ./ipv6.c.gcov 225 | ``` 226 | 227 | ## Additional Resources 228 | 229 | - [GCC Coverage Documentation](https://gcc.gnu.org/onlinedocs/gcc/Gcov.html) 230 | - [LCOV Documentation](http://ltp.sourceforge.net/coverage/lcov.php) 231 | - [Coverage Best Practices](https://en.wikipedia.org/wiki/Code_coverage) 232 | 233 | ## Troubleshooting 234 | 235 | For issues or questions: 236 | 1. Check that all prerequisites are installed 237 | 2. Verify compilation succeeded with `--coverage` 238 | 3. Ensure tests ran successfully 239 | 4. Check file permissions on `.gcda` files 240 | 241 | ## License 242 | 243 | Coverage tools and scripts: MIT License (same as IPv6-parse) 244 | -------------------------------------------------------------------------------- /test/test-sync-api.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sync API Tests 3 | * 4 | * Tests that verify the synchronous API works correctly after initialization. 5 | */ 6 | 7 | const { createParser, getParser, getAPI } = require('../index.js'); 8 | 9 | async function runTests() { 10 | console.log('Sync API Tests\n==============\n'); 11 | 12 | let passed = 0; 13 | let failed = 0; 14 | 15 | try { 16 | // Test 1: createParser returns sync methods 17 | console.log('Test 1: createParser() returns parser with sync methods'); 18 | const { parser, IPv6ParseError } = await createParser(); 19 | 20 | const addr = parser.parse('2001:db8::1'); 21 | if (addr.formatted === '2001:db8::1') { 22 | console.log('✓ parser.parse() works synchronously'); 23 | passed++; 24 | } else { 25 | console.error('✗ parser.parse() returned incorrect result'); 26 | failed++; 27 | } 28 | 29 | // Test 2: Verify sync operations don't return promises 30 | console.log('\nTest 2: Sync operations return values, not promises'); 31 | const result = parser.parse('::1'); 32 | if (!(result instanceof Promise)) { 33 | console.log('✓ parser.parse() returns value (not promise)'); 34 | passed++; 35 | } else { 36 | console.error('✗ parser.parse() incorrectly returns promise'); 37 | failed++; 38 | } 39 | 40 | // Test 3: All parser methods are sync 41 | console.log('\nTest 3: All parser methods are synchronous'); 42 | const valid = parser.isValid('::1'); 43 | const tried = parser.tryParse('invalid'); 44 | const version = parser.getVersion(); 45 | const equal = parser.equals('::1', '0:0:0:0:0:0:0:1'); 46 | 47 | if ( 48 | typeof valid === 'boolean' && 49 | tried === null && 50 | typeof version === 'string' && 51 | typeof equal === 'boolean' 52 | ) { 53 | console.log('✓ All parser methods return sync values'); 54 | passed++; 55 | } else { 56 | console.error('✗ Some parser methods returned incorrect types'); 57 | failed++; 58 | } 59 | 60 | // Test 4: getParser() returns sync parser 61 | console.log('\nTest 4: getParser() returns initialized parser'); 62 | const parser2 = getParser(); 63 | const addr2 = parser2.parse('fe80::1'); 64 | if (addr2.formatted === 'fe80::1') { 65 | console.log('✓ getParser() works correctly'); 66 | passed++; 67 | } else { 68 | console.error('✗ getParser() returned incorrect result'); 69 | failed++; 70 | } 71 | 72 | // Test 5: getAPI() returns sync functional API 73 | console.log('\nTest 5: getAPI() returns sync functional API'); 74 | const ipv6Sync = getAPI(); 75 | const addr3 = ipv6Sync.parse('192.168.1.1'); 76 | if (addr3.formatted === '192.168.1.1') { 77 | console.log('✓ getAPI() works correctly'); 78 | passed++; 79 | } else { 80 | console.error('✗ getAPI() returned incorrect result'); 81 | failed++; 82 | } 83 | 84 | // Test 6: Performance - sync operations should be fast 85 | console.log('\nTest 6: Sync operations performance'); 86 | const iterations = 10000; 87 | const start = Date.now(); 88 | 89 | for (let i = 0; i < iterations; i++) { 90 | parser.parse('2001:db8::1'); 91 | } 92 | 93 | const duration = Date.now() - start; 94 | const opsPerSec = Math.floor(iterations / (duration / 1000)); 95 | 96 | console.log(` ${iterations} parses in ${duration}ms`); 97 | console.log(` ${opsPerSec.toLocaleString()} ops/sec`); 98 | 99 | if (opsPerSec > 50000) { 100 | console.log('✓ Sync operations are performant'); 101 | passed++; 102 | } else { 103 | console.error('✗ Sync operations are slower than expected'); 104 | failed++; 105 | } 106 | 107 | // Test 7: Error handling in sync mode 108 | console.log('\nTest 7: Error handling in sync mode'); 109 | try { 110 | parser.parse('invalid'); 111 | console.error('✗ Should have thrown IPv6ParseError'); 112 | failed++; 113 | } catch (err) { 114 | if (err instanceof IPv6ParseError && err.input === 'invalid') { 115 | console.log('✓ Sync error handling works correctly'); 116 | passed++; 117 | } else { 118 | console.error('✗ Wrong error type or details'); 119 | failed++; 120 | } 121 | } 122 | 123 | // Test 8: tryParse in sync mode 124 | console.log('\nTest 8: tryParse() in sync mode'); 125 | const nullResult = parser.tryParse('definitely not valid'); 126 | const validResult = parser.tryParse('::1'); 127 | 128 | if (nullResult === null && validResult !== null && validResult.formatted === '::1') { 129 | console.log('✓ tryParse() works correctly in sync mode'); 130 | passed++; 131 | } else { 132 | console.error('✗ tryParse() returned incorrect results'); 133 | failed++; 134 | } 135 | 136 | // Test 9: Complex address parsing in sync mode 137 | console.log('\nTest 9: Complex address parsing in sync mode'); 138 | const complex = parser.parse('[2001:db8::1/64%eth0]:443'); 139 | 140 | if ( 141 | complex.formatted === '[2001:db8::1/64%eth0]:443' && // Full round-trip format 142 | complex.mask === 64 && 143 | complex.zone === 'eth0' && 144 | complex.port === 443 && 145 | complex.components[0] === 0x2001 && 146 | complex.components[1] === 0x0db8 147 | ) { 148 | console.log('✓ Complex address parsing works in sync mode'); 149 | passed++; 150 | } else { 151 | console.error('✗ Complex address parsing failed'); 152 | console.error(' Expected: [2001:db8::1/64%eth0]:443, mask=64, zone=eth0, port=443'); 153 | console.error(` Got: ${complex.formatted}, mask=${complex.mask}, zone=${complex.zone}, port=${complex.port}`); 154 | failed++; 155 | } 156 | 157 | // Test 10: getParser() throws if not initialized 158 | console.log('\nTest 10: getParser() throws if called before init'); 159 | // This requires a fresh require without initialization 160 | // We'll skip this test in the current context since we're already initialized 161 | console.log('⊘ Skipped (already initialized in this context)'); 162 | 163 | } catch (err) { 164 | console.error('Fatal error:', err); 165 | process.exit(1); 166 | } 167 | 168 | // Summary 169 | console.log('\n=============='); 170 | console.log(`Tests passed: ${passed}`); 171 | console.log(`Tests failed: ${failed}`); 172 | console.log('==============\n'); 173 | 174 | process.exit(failed > 0 ? 1 : 0); 175 | } 176 | 177 | runTests().catch(err => { 178 | console.error('Fatal error:', err); 179 | process.exit(1); 180 | }); 181 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI Build Tests 2 | 3 | on: 4 | push: 5 | pull_request: 6 | release: 7 | types: [published] 8 | schedule: 9 | - cron: '30 3 * * 0' 10 | 11 | jobs: 12 | coverage: 13 | name: Code Coverage & Memory Check 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | 18 | - name: Install Dependencies 19 | run: | 20 | sudo apt-get update 21 | sudo apt-get install -y lcov valgrind 22 | 23 | - name: Create Build Directory 24 | run: mkdir build 25 | 26 | - name: Configure with Coverage 27 | run: | 28 | cd build 29 | cmake -DENABLE_COVERAGE=1 -DIPV6_PARSE_LIBRARY_ONLY=OFF .. 30 | 31 | - name: Build 32 | run: | 33 | cd build 34 | cmake --build . -j 35 | 36 | - name: Run Tests 37 | run: | 38 | cd build 39 | ctest --output-on-failure 40 | 41 | - name: Run Extended Tests 42 | run: | 43 | cd build 44 | ./bin/ipv6-test 45 | ./bin/ipv6-fuzz 100 46 | 47 | - name: Generate Coverage Report 48 | run: | 49 | cd build 50 | lcov --capture --directory . --output-file coverage.info 51 | lcov --ignore-errors unused --remove coverage.info '/usr/*' --output-file coverage.info 52 | lcov --list coverage.info 53 | 54 | - name: Upload Coverage to Codecov 55 | uses: codecov/codecov-action@v3 56 | with: 57 | files: ./build/coverage.info 58 | fail_ci_if_error: false 59 | verbose: true 60 | 61 | - name: Run Valgrind Memory Check 62 | run: | 63 | cd build 64 | valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose --error-exitcode=1 --suppressions=../valgrind.supp ./bin/ipv6-test 65 | valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose --error-exitcode=1 --suppressions=../valgrind.supp ./bin/ipv6-fuzz 100 66 | 67 | cpp_build: 68 | runs-on: ${{ matrix.os }} 69 | strategy: 70 | matrix: 71 | include: 72 | - os: ubuntu-latest 73 | CC: gcc-12 74 | CXX: g++-12 75 | CMAKE_CMD: cmake .. -DIPV6_PARSE_LIBRARY_ONLY=OFF 76 | NAME: gcc-12 77 | 78 | - os: ubuntu-latest 79 | CC: gcc-13 80 | CXX: g++-13 81 | CMAKE_CMD: cmake .. -DIPV6_PARSE_LIBRARY_ONLY=OFF 82 | NAME: gcc-13 83 | 84 | - os: ubuntu-latest 85 | CC: gcc-14 86 | CXX: g++-14 87 | CMAKE_CMD: cmake .. -DIPV6_PARSE_LIBRARY_ONLY=OFF 88 | NAME: gcc-14 89 | 90 | - os: ubuntu-latest 91 | CC: clang-15 92 | CXX: clang++-15 93 | CMAKE_CMD: cmake .. -DIPV6_PARSE_LIBRARY_ONLY=OFF 94 | NAME: clang-15 95 | 96 | - os: ubuntu-latest 97 | CC: clang-16 98 | CXX: clang++-16 99 | CMAKE_CMD: cmake .. -DIPV6_PARSE_LIBRARY_ONLY=OFF 100 | NAME: clang-16 101 | 102 | - os: ubuntu-latest 103 | CC: clang-17 104 | CXX: clang++-17 105 | CMAKE_CMD: cmake .. -DIPV6_PARSE_LIBRARY_ONLY=OFF 106 | NAME: clang-17 107 | 108 | - os: ubuntu-latest 109 | CC: clang-18 110 | CXX: clang++-18 111 | CMAKE_CMD: cmake .. -DIPV6_PARSE_LIBRARY_ONLY=OFF 112 | NAME: clang-18 113 | 114 | - os: macos-latest 115 | CMAKE_CMD: cmake .. -DIPV6_PARSE_LIBRARY_ONLY=OFF 116 | NAME: AppleClang 117 | 118 | - os: windows-latest 119 | CMAKE_CMD: cmake .. -DIPV6_PARSE_LIBRARY_ONLY=OFF 120 | NAME: msvc 121 | 122 | name: ${{ matrix.os }}-${{ matrix.NAME }} - C++ Test 123 | env: 124 | MSBUILD_PATH: "C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/MSBuild/Current/Bin" 125 | steps: 126 | - name: Checkout code 127 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 128 | with: 129 | submodules: recursive 130 | - name: Create Build Directory 131 | run: mkdir build 132 | 133 | - name: Install Dependencies (Linux) 134 | if: matrix.os == 'ubuntu-latest' 135 | run: | 136 | sudo apt update 137 | sudo apt install -y ${{ matrix.CC }} || echo "Compiler already installed" 138 | if [[ "${{ matrix.CXX }}" != *"clang"* ]]; then sudo apt install -y ${{ matrix.CXX }} || echo "C++ compiler already installed"; fi 139 | 140 | - name: Execute CMake Process 141 | env: 142 | CC: ${{ matrix.CC }} 143 | CXX: ${{ matrix.CXX }} 144 | run: | 145 | cd build 146 | ${{ matrix.CMAKE_CMD }} 147 | - name: Build Project 148 | run: | 149 | cd build 150 | cmake --build . -j --config Release 151 | 152 | - name: Run Tests 153 | run: | 154 | cd build 155 | ctest --output-on-failure -j -C Release 156 | 157 | test-nodejs: 158 | name: Node.js Tests and Linting 159 | runs-on: ubuntu-latest 160 | steps: 161 | - name: Checkout code 162 | uses: actions/checkout@v4 163 | 164 | - name: Setup Node.js 165 | uses: actions/setup-node@v4 166 | with: 167 | node-version: '18' 168 | 169 | - name: Install dependencies 170 | run: npm install 171 | 172 | - name: Run ESLint 173 | run: npm run lint 174 | 175 | - name: Setup Emscripten 176 | uses: mymindstorm/setup-emsdk@v14 177 | with: 178 | version: latest 179 | 180 | - name: Build WASM 181 | run: ./build_wasm.sh 182 | 183 | - name: Validate build output 184 | run: npm run validate 185 | 186 | - name: Run Node.js tests 187 | run: npm run test:node 188 | 189 | - name: Run WASM tests 190 | run: npm run test:wasm 191 | 192 | - name: Run API tests 193 | run: npm run test:api 194 | 195 | test-conan: 196 | name: Conan Package Test 197 | runs-on: ${{ matrix.os }} 198 | strategy: 199 | fail-fast: false 200 | matrix: 201 | os: [ubuntu-latest, macos-latest, windows-latest] 202 | steps: 203 | - name: Checkout code 204 | uses: actions/checkout@v4 205 | 206 | - name: Set up Python 207 | uses: actions/setup-python@v5 208 | with: 209 | python-version: '3.11' 210 | 211 | - name: Install Conan 212 | run: | 213 | pip install conan 214 | conan profile detect --force 215 | 216 | - name: Test Conan package 217 | run: conan create . --build=missing 218 | -------------------------------------------------------------------------------- /docs/diagnostic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IPv6 Parser Diagnostic 6 | 13 | 14 | 15 |

IPv6 Parser Diagnostic

16 |

 17 | 
 18 |     
140 | 
141 | 
142 | 


--------------------------------------------------------------------------------
/test/validate-build.js:
--------------------------------------------------------------------------------
  1 | /**
  2 |  * Build output validation script
  3 |  * Validates that all necessary files are present and properly formatted
  4 |  */
  5 | 
  6 | const fs = require('fs');
  7 | const path = require('path');
  8 | 
  9 | console.log('Build Output Validation\n========================\n');
 10 | 
 11 | let errors = 0;
 12 | let warnings = 0;
 13 | 
 14 | // Required files
 15 | const requiredFiles = [
 16 |   'docs/ipv6-parse.js',
 17 |   'docs/ipv6-parse-api.js',
 18 |   'docs/ipv6-parse-api.d.ts',
 19 |   'docs/index.html',
 20 |   'docs/README.md',
 21 |   'index.js',
 22 |   'index.d.ts',
 23 |   'package.json',
 24 |   'README.md',
 25 |   'README_WASM.md',
 26 |   'README_NPM.md'
 27 | ];
 28 | 
 29 | console.log('Checking required files...');
 30 | for (const file of requiredFiles) {
 31 |   const filePath = path.join(__dirname, '..', file);
 32 |   if (fs.existsSync(filePath)) {
 33 |     const stats = fs.statSync(filePath);
 34 |     const sizeKB = (stats.size / 1024).toFixed(2);
 35 |     console.log(`  ✓ ${file} (${sizeKB} KB)`);
 36 |   } else {
 37 |     console.error(`  ✗ Missing: ${file}`);
 38 |     errors++;
 39 |   }
 40 | }
 41 | 
 42 | // Check WASM module contents
 43 | console.log('\nValidating WASM module...');
 44 | const wasmPath = path.join(__dirname, '../docs/ipv6-parse.js');
 45 | if (fs.existsSync(wasmPath)) {
 46 |   const content = fs.readFileSync(wasmPath, 'utf8');
 47 | 
 48 |   // Check for Emscripten exports
 49 |   const requiredExports = [
 50 |     'createIPv6Module',
 51 |     'UTF8ToString',
 52 |     'ccall'
 53 |   ];
 54 | 
 55 |   for (const exp of requiredExports) {
 56 |     if (content.includes(exp)) {
 57 |       console.log(`  ✓ Export: ${exp}`);
 58 |     } else {
 59 |       console.error(`  ✗ Missing export: ${exp}`);
 60 |       errors++;
 61 |     }
 62 |   }
 63 | 
 64 |   // Check for WASM functions
 65 |   const requiredFunctions = [
 66 |     'ipv6_parse_full',
 67 |     'ipv6_compare_str',
 68 |     'ipv6_version',
 69 |     'ipv6_result_size'
 70 |   ];
 71 | 
 72 |   for (const func of requiredFunctions) {
 73 |     if (content.includes(func)) {
 74 |       console.log(`  ✓ Function: ${func}`);
 75 |     } else {
 76 |       console.error(`  ✗ Missing function: ${func}`);
 77 |       errors++;
 78 |     }
 79 |   }
 80 | } else {
 81 |   console.error('  ✗ WASM module not found');
 82 |   errors++;
 83 | }
 84 | 
 85 | // Check API layer
 86 | console.log('\nValidating API layer...');
 87 | const apiPath = path.join(__dirname, '../docs/ipv6-parse-api.js');
 88 | if (fs.existsSync(apiPath)) {
 89 |   const content = fs.readFileSync(apiPath, 'utf8');
 90 | 
 91 |   const requiredClasses = [
 92 |     'class IPv6Parser',
 93 |     'class IPv6Address',
 94 |     'class IPv6ParseError'
 95 |   ];
 96 | 
 97 |   for (const cls of requiredClasses) {
 98 |     if (content.includes(cls)) {
 99 |       console.log(`  ✓ ${cls}`);
100 |     } else {
101 |       console.error(`  ✗ Missing: ${cls}`);
102 |       errors++;
103 |     }
104 |   }
105 | 
106 |   const requiredMethods = [
107 |     'parse(',
108 |     'tryParse(',
109 |     'isValid(',
110 |     'equals(',
111 |     'getVersion('
112 |   ];
113 | 
114 |   for (const method of requiredMethods) {
115 |     if (content.includes(method)) {
116 |       console.log(`  ✓ Method: ${method}`);
117 |     } else {
118 |       console.error(`  ✗ Missing method: ${method}`);
119 |       errors++;
120 |     }
121 |   }
122 | } else {
123 |   console.error('  ✗ API layer not found');
124 |   errors++;
125 | }
126 | 
127 | // Check TypeScript definitions
128 | console.log('\nValidating TypeScript definitions...');
129 | const dtsFiles = [
130 |   'docs/ipv6-parse-api.d.ts',
131 |   'index.d.ts'
132 | ];
133 | 
134 | for (const file of dtsFiles) {
135 |   const filePath = path.join(__dirname, '..', file);
136 |   if (fs.existsSync(filePath)) {
137 |     const content = fs.readFileSync(filePath, 'utf8');
138 | 
139 |     const requiredTypes = [
140 |       'interface IPv6Address',
141 |       'class IPv6ParseError',
142 |       'interface ComparisonOptions'
143 |     ];
144 | 
145 |     let fileErrors = 0;
146 |     for (const type of requiredTypes) {
147 |       if (content.includes(type)) {
148 |         console.log(`  ✓ ${file}: ${type}`);
149 |       } else {
150 |         console.error(`  ✗ ${file}: Missing ${type}`);
151 |         fileErrors++;
152 |       }
153 |     }
154 | 
155 |     if (fileErrors === 0) {
156 |       console.log(`  ✓ ${file} is valid`);
157 |     } else {
158 |       errors += fileErrors;
159 |     }
160 |   } else {
161 |     console.error(`  ✗ ${file} not found`);
162 |     errors++;
163 |   }
164 | }
165 | 
166 | // Check package.json
167 | console.log('\nValidating package.json...');
168 | const pkgPath = path.join(__dirname, '../package.json');
169 | if (fs.existsSync(pkgPath)) {
170 |   const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
171 | 
172 |   const requiredFields = ['name', 'version', 'main', 'types', 'scripts'];
173 |   for (const field of requiredFields) {
174 |     if (pkg[field]) {
175 |       console.log(`  ✓ Field: ${field}`);
176 |     } else {
177 |       console.error(`  ✗ Missing field: ${field}`);
178 |       errors++;
179 |     }
180 |   }
181 | 
182 |   // Check scripts
183 |   const requiredScripts = ['test', 'build', 'lint'];
184 |   for (const script of requiredScripts) {
185 |     if (pkg.scripts && pkg.scripts[script]) {
186 |       console.log(`  ✓ Script: ${script}`);
187 |     } else {
188 |       console.error(`  ✗ Missing script: ${script}`);
189 |       errors++;
190 |     }
191 |   }
192 | 
193 |   // Check files array
194 |   if (pkg.files && Array.isArray(pkg.files)) {
195 |     console.log(`  ✓ Files array: ${pkg.files.length} entries`);
196 |   } else {
197 |     console.error('  ✗ Missing or invalid files array');
198 |     errors++;
199 |   }
200 | } else {
201 |   console.error('  ✗ package.json not found');
202 |   errors++;
203 | }
204 | 
205 | // Check demo page
206 | console.log('\nValidating demo page...');
207 | const demoPath = path.join(__dirname, '../docs/index.html');
208 | if (fs.existsSync(demoPath)) {
209 |   const content = fs.readFileSync(demoPath, 'utf8');
210 | 
211 |   const requiredElements = [
212 |     'ipv6-parse.js',
213 |     'ipv6-parse-api.js',
214 |     'IPv6Parser',
215 |     ' 0) {
241 |     console.log('\n✗ Build validation failed!');
242 |   } else {
243 |     console.log('\n⚠ Build validation passed with warnings');
244 |   }
245 | }
246 | console.log('========================\n');
247 | 
248 | process.exit(errors > 0 ? 1 : 0);
249 | 


--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
  1 | /**
  2 |  * Type definitions for ipv6-parse (Node.js)
  3 |  *
  4 |  * High-performance IPv6/IPv4 address parser with full RFC compliance
  5 |  */
  6 | 
  7 | /**
  8 |  * Parsed IPv6/IPv4 address with all metadata
  9 |  */
 10 | export interface IPv6Address {
 11 |   /** RFC 5952 formatted address string */
 12 |   readonly formatted: string;
 13 | 
 14 |   /** Address components (8 x uint16) */
 15 |   readonly components: readonly number[];
 16 | 
 17 |   /** Port number or null if not specified */
 18 |   readonly port: number | null;
 19 | 
 20 |   /** CIDR mask bits or null if not specified */
 21 |   readonly mask: number | null;
 22 | 
 23 |   /** Zone ID (interface name) or null if not specified */
 24 |   readonly zone: string | null;
 25 | 
 26 |   /** True if address has port specified */
 27 |   readonly hasPort: boolean;
 28 | 
 29 |   /** True if address has CIDR mask specified */
 30 |   readonly hasMask: boolean;
 31 | 
 32 |   /** True if address has IPv4 embedded (::ffff:192.0.2.1) */
 33 |   readonly isIPv4Embedded: boolean;
 34 | 
 35 |   /** True if address is IPv4 compatible (::192.0.2.1) */
 36 |   readonly isIPv4Compatible: boolean;
 37 | 
 38 |   /** Get hexadecimal representation of component */
 39 |   getComponentHex(index: number): string;
 40 | 
 41 |   /** Convert to plain object */
 42 |   toJSON(): object;
 43 | 
 44 |   /** Convert to string (returns formatted address) */
 45 |   toString(): string;
 46 | }
 47 | 
 48 | /**
 49 |  * Custom error for IPv6 parsing failures
 50 |  */
 51 | export class IPv6ParseError extends Error {
 52 |   /** Error message */
 53 |   readonly message: string;
 54 | 
 55 |   /** Input that failed to parse */
 56 |   readonly input: string;
 57 | 
 58 |   constructor(message: string, input: string);
 59 | }
 60 | 
 61 | /**
 62 |  * Comparison options
 63 |  */
 64 | export interface ComparisonOptions {
 65 |   /** Ignore port in comparison */
 66 |   ignorePort?: boolean;
 67 | 
 68 |   /** Ignore CIDR mask in comparison */
 69 |   ignoreMask?: boolean;
 70 | 
 71 |   /** Ignore zone ID in comparison */
 72 |   ignoreZone?: boolean;
 73 | }
 74 | 
 75 | /**
 76 |  * IPv6/IPv4 Address Parser class
 77 |  */
 78 | export class IPv6Parser {
 79 |   /**
 80 |    * Parse an IPv6 or IPv4 address
 81 |    * @param address - Address to parse (e.g., "::1", "[::1]:8080", "fe80::1%eth0")
 82 |    * @returns Parsed address object
 83 |    * @throws IPv6ParseError if address is invalid
 84 |    */
 85 |   parse(address: string): IPv6Address;
 86 | 
 87 |   /**
 88 |    * Try to parse an address, returning null on failure instead of throwing
 89 |    * @param address - Address to parse
 90 |    * @returns Parsed address or null if invalid
 91 |    */
 92 |   tryParse(address: string): IPv6Address | null;
 93 | 
 94 |   /**
 95 |    * Check if a string is a valid address
 96 |    * @param address - Address to validate
 97 |    * @returns True if valid
 98 |    */
 99 |   isValid(address: string): boolean;
100 | 
101 |   /**
102 |    * Compare two addresses for equality
103 |    * @param addr1 - First address
104 |    * @param addr2 - Second address
105 |    * @param options - Comparison options
106 |    * @returns True if addresses are equal
107 |    */
108 |   equals(addr1: string, addr2: string, options?: ComparisonOptions): boolean;
109 | 
110 |   /**
111 |    * Get library version
112 |    * @returns Version string
113 |    */
114 |   getVersion(): string;
115 | 
116 |   /**
117 |    * Clean up allocated memory
118 |    */
119 |   destroy(): void;
120 | }
121 | 
122 | /**
123 |  * Functional API object
124 |  */
125 | export interface IPv6API {
126 |   parse(address: string): IPv6Address;
127 |   tryParse(address: string): IPv6Address | null;
128 |   isValid(address: string): boolean;
129 |   equals(addr1: string, addr2: string, options?: ComparisonOptions): boolean;
130 |   getVersion(): string;
131 | }
132 | 
133 | /**
134 |  * Parser initialization result
135 |  */
136 | export interface ParserAPI {
137 |   parser: IPv6Parser;
138 |   IPv6Parser: typeof IPv6Parser;
139 |   IPv6Address: typeof IPv6Address;
140 |   IPv6ParseError: typeof IPv6ParseError;
141 |   ipv6: IPv6API;
142 | }
143 | 
144 | // ============================================================================
145 | // Async Convenience API (auto-initializes on first use)
146 | // ============================================================================
147 | // Use these for simple scripts where convenience matters more than performance.
148 | // These functions auto-initialize on first use, so they're async.
149 | 
150 | /**
151 |  * Parse an IPv6/IPv4 address (async, auto-initializes)
152 |  * @param address - Address to parse
153 |  * @returns Promise that resolves to parsed address object
154 |  * @throws IPv6ParseError if address is invalid
155 |  */
156 | export function parse(address: string): Promise;
157 | 
158 | /**
159 |  * Try to parse an address, returning null on failure (async, auto-initializes)
160 |  * @param address - Address to parse
161 |  * @returns Promise that resolves to parsed address or null
162 |  */
163 | export function tryParse(address: string): Promise;
164 | 
165 | /**
166 |  * Check if an address is valid (async, auto-initializes)
167 |  * @param address - Address to validate
168 |  * @returns Promise that resolves to true if valid
169 |  */
170 | export function isValid(address: string): Promise;
171 | 
172 | /**
173 |  * Compare two addresses for equality (async, auto-initializes)
174 |  * @param addr1 - First address
175 |  * @param addr2 - Second address
176 |  * @param options - Comparison options
177 |  * @returns Promise that resolves to true if equal
178 |  */
179 | export function equals(
180 |   addr1: string,
181 |   addr2: string,
182 |   options?: ComparisonOptions
183 | ): Promise;
184 | 
185 | /**
186 |  * Get library version (async, auto-initializes)
187 |  * @returns Promise that resolves to version string
188 |  */
189 | export function getVersion(): Promise;
190 | 
191 | // ============================================================================
192 | // Sync Explicit API (requires explicit initialization)
193 | // ============================================================================
194 | // Use these for performance-critical code or when you need synchronous operations.
195 | // Initialize once with createParser(), then use the sync methods.
196 | 
197 | /**
198 |  * Initialize the IPv6 parser
199 |  * Call this once at startup, then use parser.parse() synchronously.
200 |  * @returns Promise that resolves to the parser API with sync methods
201 |  * @example
202 |  * const { parser } = await createParser();
203 |  * const addr = parser.parse('2001:db8::1'); // Synchronous!
204 |  */
205 | export function createParser(): Promise;
206 | 
207 | /**
208 |  * Get synchronous parser instance (requires prior initialization)
209 |  * @returns Parser instance with synchronous methods
210 |  * @throws Error if not initialized (call createParser() first)
211 |  * @example
212 |  * await createParser();
213 |  * const parser = getParser();
214 |  * const addr = parser.parse('::1'); // Synchronous!
215 |  */
216 | export function getParser(): IPv6Parser;
217 | 
218 | /**
219 |  * Get synchronous functional API (requires prior initialization)
220 |  * @returns Functional API object with synchronous methods
221 |  * @throws Error if not initialized (call createParser() first)
222 |  * @example
223 |  * await createParser();
224 |  * const ipv6 = getAPI();
225 |  * const addr = ipv6.parse('::1'); // Synchronous!
226 |  */
227 | export function getAPI(): IPv6API;
228 | 
229 | // Export types
230 | export { IPv6Address, IPv6ParseError, IPv6Parser, ComparisonOptions };
231 | 


--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
  1 | # Contributing to ipv6-parse
  2 | 
  3 | Thank you for your interest in contributing to ipv6-parse! This document provides guidelines and instructions for contributing to the project.
  4 | 
  5 | ## Table of Contents
  6 | 
  7 | - [Development Setup](#development-setup)
  8 | - [Building](#building)
  9 | - [Testing](#testing)
 10 | - [Code Quality](#code-quality)
 11 | - [Submitting Changes](#submitting-changes)
 12 | - [Project Structure](#project-structure)
 13 | 
 14 | ## Development Setup
 15 | 
 16 | ### Prerequisites
 17 | 
 18 | 1. **C Compiler** (for native library):
 19 |    - GCC 12+ or Clang 15+ (Linux/macOS)
 20 |    - MSVC (Windows)
 21 | 
 22 | 2. **CMake** 3.12+
 23 | 
 24 | 3. **Node.js** 12+ and npm (for JavaScript/WASM)
 25 | 
 26 | 4. **Emscripten SDK** (for WebAssembly):
 27 |    ```bash
 28 |    git clone https://github.com/emscripten-core/emsdk.git
 29 |    cd emsdk
 30 |    ./emsdk install latest
 31 |    ./emsdk activate latest
 32 |    source ./emsdk_env.sh
 33 |    ```
 34 | 
 35 | ### Initial Setup
 36 | 
 37 | ```bash
 38 | # Clone the repository
 39 | git clone https://github.com/jrepp/ipv6-parse.git
 40 | cd ipv6-parse
 41 | 
 42 | # Install Node.js dependencies
 43 | npm install
 44 | 
 45 | # Build WASM module (requires Emscripten)
 46 | ./build_wasm.sh
 47 | 
 48 | # Or build C library
 49 | mkdir build && cd build
 50 | cmake ..
 51 | make
 52 | ```
 53 | 
 54 | ## Building
 55 | 
 56 | ### C Library
 57 | 
 58 | **Static library (default):**
 59 | ```bash
 60 | mkdir build && cd build
 61 | cmake ..
 62 | make
 63 | ```
 64 | 
 65 | **Shared library:**
 66 | ```bash
 67 | mkdir build && cd build
 68 | cmake -DBUILD_SHARED_LIBS=ON ..
 69 | make
 70 | ```
 71 | 
 72 | **With code coverage:**
 73 | ```bash
 74 | mkdir build && cd build
 75 | cmake -DENABLE_COVERAGE=ON ..
 76 | make
 77 | ```
 78 | 
 79 | ### WebAssembly
 80 | 
 81 | ```bash
 82 | # Ensure Emscripten is activated
 83 | source /path/to/emsdk/emsdk_env.sh
 84 | 
 85 | # Build WASM module
 86 | ./build_wasm.sh
 87 | 
 88 | # Clean build
 89 | ./build_wasm.sh clean
 90 | ```
 91 | 
 92 | ## Testing
 93 | 
 94 | ### Quick Test (All Tests)
 95 | 
 96 | ```bash
 97 | ./test.sh
 98 | ```
 99 | 
100 | This runs all tests in the correct order:
101 | 1. C library tests
102 | 2. WebAssembly build
103 | 3. ESLint
104 | 4. Build validation
105 | 5. Node.js tests
106 | 6. WASM tests
107 | 7. API tests
108 | 
109 | ### Individual Test Suites
110 | 
111 | **C Library Tests:**
112 | ```bash
113 | cd build
114 | ctest --output-on-failure
115 | ```
116 | 
117 | **Node.js Tests:**
118 | ```bash
119 | npm run test:node        # Node.js wrapper tests
120 | npm run test:wasm        # WASM module tests
121 | npm run test:api         # JavaScript API tests
122 | ```
123 | 
124 | **All JavaScript Tests:**
125 | ```bash
126 | npm test                 # Runs all JS tests + linting
127 | ```
128 | 
129 | ### Code Coverage
130 | 
131 | ```bash
132 | mkdir build && cd build
133 | cmake -DENABLE_COVERAGE=ON ..
134 | make
135 | ctest
136 | lcov --capture --directory . --output-file coverage.info
137 | lcov --remove coverage.info '/usr/*' --output-file coverage.info
138 | genhtml coverage.info --output-directory coverage
139 | ```
140 | 
141 | Open `coverage/index.html` to view coverage report.
142 | 
143 | ## Code Quality
144 | 
145 | ### Linting
146 | 
147 | **JavaScript/Node.js:**
148 | ```bash
149 | npm run lint             # Check for issues
150 | npm run lint:fix         # Auto-fix issues
151 | ```
152 | 
153 | **C Code:**
154 | - Follow existing code style
155 | - Use `-Wall -Werror -pedantic` flags
156 | - Keep functions focused and well-documented
157 | 
158 | ### Code Style
159 | 
160 | **C Code:**
161 | - Use snake_case for functions and variables
162 | - Use UPPER_CASE for macros and constants
163 | - Comment all public APIs
164 | - Keep lines under 100 characters
165 | - Use `const` where appropriate
166 | 
167 | **JavaScript Code:**
168 | - ES6+ syntax (classes, arrow functions, const/let)
169 | - Use single quotes for strings
170 | - 2-space indentation
171 | - Semicolons required
172 | - Follow ESLint rules (see `.eslintrc.json`)
173 | 
174 | ### Build Validation
175 | 
176 | Before submitting a PR, ensure build output is valid:
177 | 
178 | ```bash
179 | npm run validate
180 | ```
181 | 
182 | This checks:
183 | - All required files exist
184 | - WASM module is properly built
185 | - API layer exports all classes
186 | - TypeScript definitions are complete
187 | - package.json is valid
188 | 
189 | ## Submitting Changes
190 | 
191 | ### Pull Request Process
192 | 
193 | 1. **Create a feature branch:**
194 |    ```bash
195 |    git checkout -b feature/my-new-feature
196 |    ```
197 | 
198 | 2. **Make your changes:**
199 |    - Write code following style guidelines
200 |    - Add tests for new functionality
201 |    - Update documentation as needed
202 | 
203 | 3. **Run tests locally:**
204 |    ```bash
205 |    ./test.sh
206 |    ```
207 | 
208 | 4. **Commit your changes:**
209 |    ```bash
210 |    git add .
211 |    git commit -m "Add feature: description"
212 |    ```
213 | 
214 |    Commit message format:
215 |    - Use present tense ("Add feature" not "Added feature")
216 |    - Keep first line under 72 characters
217 |    - Add detailed description in body if needed
218 | 
219 | 5. **Push and create PR:**
220 |    ```bash
221 |    git push origin feature/my-new-feature
222 |    ```
223 | 
224 |    Then open a pull request on GitHub.
225 | 
226 | ### PR Requirements
227 | 
228 | - [ ] All tests pass (`./test.sh`)
229 | - [ ] Code follows style guidelines
230 | - [ ] New tests added for new features
231 | - [ ] Documentation updated (if applicable)
232 | - [ ] No linting errors (`npm run lint`)
233 | - [ ] Build validation passes (`npm run validate`)
234 | 
235 | ### CI/CD
236 | 
237 | All PRs are automatically tested via GitHub Actions:
238 | - Multi-platform builds (Linux, macOS, Windows)
239 | - Multi-compiler testing (GCC, Clang, MSVC)
240 | - Code coverage analysis
241 | - Memory leak detection (Valgrind)
242 | - ESLint checks
243 | - Node.js tests
244 | - WASM tests
245 | 
246 | ## Project Structure
247 | 
248 | ```
249 | ipv6-parse/
250 | ├── ipv6.h                      # Public C API header
251 | ├── ipv6.c                      # Core C implementation
252 | ├── ipv6_wasm.c                 # WASM bindings
253 | ├── ipv6_config.h.in            # CMake configuration template
254 | │
255 | ├── index.js                    # Node.js entry point
256 | ├── index.d.ts                  # Node.js TypeScript definitions
257 | ├── package.json                # NPM package configuration
258 | │
259 | ├── docs/                       # WebAssembly distribution
260 | │   ├── index.html             # Interactive demo
261 | │   ├── ipv6-parse.js          # WASM module (generated)
262 | │   ├── ipv6-parse-api.js      # JavaScript API layer
263 | │   └── ipv6-parse-api.d.ts    # TypeScript definitions
264 | │
265 | ├── test/                       # Test suite
266 | │   ├── test-node.js           # Node.js wrapper tests
267 | │   ├── test-wasm.js           # WASM module tests
268 | │   ├── test-api.js            # API layer tests
269 | │   ├── check-wasm.js          # Pre-test WASM check
270 | │   └── validate-build.js      # Build validation
271 | │
272 | ├── cmake/                      # CMake modules
273 | │   ├── emscripten.cmake       # Emscripten toolchain
274 | │   └── ipv6-parse-config.cmake.in
275 | │
276 | ├── debian/                     # Debian packaging
277 | ├── .github/workflows/          # CI/CD pipelines
278 | │   ├── ci.yml                 # Continuous integration
279 | │   └── release.yml            # Release automation
280 | │
281 | ├── CMakeLists.txt             # CMake configuration
282 | ├── build_wasm.sh              # WASM build script
283 | ├── test.sh                    # Comprehensive test script
284 | │
285 | └── README.md                  # Main documentation
286 | ```
287 | 
288 | ## Questions?
289 | 
290 | - Open an issue on GitHub
291 | - Email: jacobrepp@gmail.com
292 | 
293 | ## License
294 | 
295 | By contributing to ipv6-parse, you agree that your contributions will be licensed under the MIT License.
296 | 


--------------------------------------------------------------------------------
/README_NPM.md:
--------------------------------------------------------------------------------
  1 | # ipv6-parse (NPM Package)
  2 | 
  3 | High-performance IPv6/IPv4 address parser with full RFC compliance, compiled to WebAssembly.
  4 | 
  5 | **[View on NPM](https://www.npmjs.com/package/ipv6-parse)** | **[GitHub](https://github.com/jrepp/ipv6-parse)**
  6 | 
  7 | ## Features
  8 | 
  9 | - ✅ **Full RFC Compliance**: RFC 4291, RFC 5952, RFC 4007
 10 | - ⚡ **High Performance**: 1.75M+ parses/sec, validated by benchmarks
 11 | - 📦 **Zero Dependencies**: Pure WebAssembly, no external dependencies
 12 | - 🎯 **TypeScript Support**: Complete type definitions included
 13 | - 🔄 **Dual API**: Async convenience + Sync explicit for max performance
 14 | - 🌐 **Comprehensive**: Supports ports, CIDR masks, zone IDs, IPv4 embedding
 15 | 
 16 | ## Installation
 17 | 
 18 | ```bash
 19 | npm install ipv6-parse
 20 | ```
 21 | 
 22 | ## Quick Start
 23 | 
 24 | ### Async Convenience API (Simple Scripts)
 25 | 
 26 | Perfect for scripts where convenience matters more than performance:
 27 | 
 28 | ```javascript
 29 | const ipv6 = require('ipv6-parse');
 30 | 
 31 | async function main() {
 32 |   // Parse an address (auto-initializes on first use)
 33 |   const addr = await ipv6.parse('2001:db8::1/64');
 34 |   console.log(addr.formatted);  // "2001:db8::1"
 35 |   console.log(addr.mask);        // 64
 36 | 
 37 |   // Validate
 38 |   if (await ipv6.isValid('::1')) {
 39 |     console.log('Valid!');
 40 |   }
 41 | 
 42 |   // Try parse (returns null on error)
 43 |   const result = await ipv6.tryParse('invalid');
 44 |   console.log(result);  // null
 45 | }
 46 | 
 47 | main();
 48 | ```
 49 | 
 50 | ### Sync Explicit API (High Performance)
 51 | 
 52 | Initialize once, then parse synchronously. Best for servers and hot code paths:
 53 | 
 54 | ```javascript
 55 | const { createParser } = require('ipv6-parse');
 56 | 
 57 | async function main() {
 58 |   // Initialize once at startup (async)
 59 |   const { parser } = await createParser();
 60 | 
 61 |   // All operations now synchronous!
 62 |   const addr = parser.parse('2001:db8::1');  // Sync!
 63 |   console.log(addr.formatted);
 64 | 
 65 |   // Parse 1 million addresses synchronously
 66 |   for (let i = 0; i < 1000000; i++) {
 67 |     parser.parse('::1');  // No async overhead!
 68 |   }
 69 | }
 70 | 
 71 | main();
 72 | ```
 73 | 
 74 | ## TypeScript
 75 | 
 76 | Full type safety with comprehensive definitions:
 77 | 
 78 | ```typescript
 79 | import { parse, IPv6Address } from 'ipv6-parse';
 80 | 
 81 | async function example() {
 82 |   const addr: IPv6Address = await parse('2001:db8::1');
 83 |   console.log(addr.formatted);  // Full IDE autocomplete!
 84 | }
 85 | ```
 86 | 
 87 | ## API Reference
 88 | 
 89 | ### When to Use Which API?
 90 | 
 91 | | Use Case | API | Why? |
 92 | |----------|-----|------|
 93 | | Simple scripts, CLI tools | **Async Convenience** | Auto-init, minimal code |
 94 | | Web servers, middleware | **Sync Explicit** | Max performance |
 95 | | Performance-critical loops | **Sync Explicit** | No async overhead |
 96 | | Batch processing | **Sync Explicit** | 1.75M+ parses/sec |
 97 | 
 98 | ### Async Convenience API
 99 | 
100 | Auto-initializes on first use:
101 | 
102 | - `parse(address): Promise` - Parse, throws on error
103 | - `tryParse(address): Promise` - Parse, returns null on error
104 | - `isValid(address): Promise` - Validate address
105 | - `equals(addr1, addr2, options?): Promise` - Compare addresses
106 | - `getVersion(): Promise` - Get library version
107 | 
108 | ### Sync Explicit API
109 | 
110 | Initialize once, parse synchronously:
111 | 
112 | ```javascript
113 | const { createParser } = require('ipv6-parse');
114 | const { parser } = await createParser();
115 | 
116 | // All methods are now synchronous:
117 | parser.parse(address)        // Sync - throws on error
118 | parser.tryParse(address)     // Sync - returns null on error
119 | parser.isValid(address)      // Sync - returns boolean
120 | parser.equals(addr1, addr2)  // Sync - returns boolean
121 | parser.getVersion()          // Sync - returns string
122 | ```
123 | 
124 | ### IPv6Address Object
125 | 
126 | All parsed addresses have these properties:
127 | 
128 | ```typescript
129 | {
130 |   formatted: string;           // RFC 5952 canonical form
131 |   components: number[];        // 8 x uint16 components
132 |   port: number | null;         // Port number or null
133 |   mask: number | null;         // CIDR mask or null
134 |   zone: string | null;         // Zone ID or null
135 |   hasPort: boolean;
136 |   hasMask: boolean;
137 |   isIPv4Embedded: boolean;
138 |   isIPv4Compatible: boolean;
139 | 
140 |   getComponentHex(index): string;
141 |   toJSON(): object;
142 |   toString(): string;
143 | }
144 | ```
145 | 
146 | ### Comparison Options
147 | 
148 | ```javascript
149 | parser.equals('[::1]:80', '[::1]:443', {
150 |   ignorePort: true,    // Ignore port differences
151 |   ignoreMask: true,    // Ignore CIDR mask differences
152 |   ignoreFormat: true   // Ignore IPv4 embedding format
153 | });
154 | ```
155 | 
156 | ## Supported Formats
157 | 
158 | Parses all standard IPv6/IPv4 address formats:
159 | 
160 | ```javascript
161 | // Pure IPv6
162 | '2001:db8::1', '::1', 'fe80::1'
163 | 
164 | // IPv4
165 | '192.168.1.1', '10.0.0.1'
166 | 
167 | // IPv4-embedded IPv6
168 | '::ffff:192.0.2.1'
169 | 
170 | // With CIDR mask
171 | '2001:db8::/32', '10.0.0.0/8'
172 | 
173 | // With port
174 | '[::1]:8080', '192.168.1.1:80'
175 | 
176 | // With zone ID (RFC 4007)
177 | 'fe80::1%eth0', 'fe80::1%en0'
178 | 
179 | // Combined
180 | '[2001:db8::1/64%eth0]:443'
181 | ```
182 | 
183 | ## Performance
184 | 
185 | Validated by comprehensive benchmarks (`npm run bench`):
186 | 
187 | ```
188 | ✓ Throughput: 1,754,386 parses/second
189 | ✓ Latency: 570 nanoseconds per parse
190 | ✓ 2.71x faster than naive multi-call approach
191 | ✓ 93% fewer WASM boundary crossings
192 | ```
193 | 
194 | Run benchmarks yourself:
195 | ```bash
196 | npm run bench
197 | ```
198 | 
199 | ## Testing
200 | 
201 | All tests passing (36/36):
202 | 
203 | ```bash
204 | npm test              # Run all tests
205 | npm run test:node     # Node.js wrapper tests
206 | npm run test:wasm     # WASM module tests
207 | npm run test:sync     # Sync API tests
208 | npm run test:errors   # Error handling tests
209 | npm run bench         # Performance benchmarks
210 | ```
211 | 
212 | ## Error Handling
213 | 
214 | ```javascript
215 | const { createParser, IPv6ParseError } = require('ipv6-parse');
216 | const { parser } = await createParser();
217 | 
218 | try {
219 |   const addr = parser.parse('invalid');
220 | } catch (err) {
221 |   if (err instanceof IPv6ParseError) {
222 |     console.error('Parse error:', err.message);
223 |     console.error('Input was:', err.input);
224 |   }
225 | }
226 | ```
227 | 
228 | ## Examples
229 | 
230 | ### Express Middleware
231 | 
232 | ```javascript
233 | const { createParser } = require('ipv6-parse');
234 | 
235 | // Initialize once at startup
236 | let parser;
237 | createParser().then(api => { parser = api.parser; });
238 | 
239 | app.use((req, res, next) => {
240 |   const ip = req.ip;
241 |   if (parser.isValid(ip)) {
242 |     req.parsedIP = parser.parse(ip);
243 |   }
244 |   next();
245 | });
246 | ```
247 | 
248 | ### CLI Tool
249 | 
250 | ```javascript
251 | #!/usr/bin/env node
252 | const ipv6 = require('ipv6-parse');
253 | 
254 | async function main() {
255 |   const address = process.argv[2];
256 | 
257 |   const addr = await ipv6.tryParse(address);
258 |   if (addr) {
259 |     console.log('Valid:', JSON.stringify(addr.toJSON(), null, 2));
260 |   } else {
261 |     console.error('Invalid address');
262 |     process.exit(1);
263 |   }
264 | }
265 | 
266 | main();
267 | ```
268 | 
269 | ### Batch Processing
270 | 
271 | ```javascript
272 | const { createParser } = require('ipv6-parse');
273 | 
274 | async function processLogFile(lines) {
275 |   const { parser } = await createParser();
276 | 
277 |   const addresses = [];
278 |   for (const line of lines) {
279 |     const addr = parser.tryParse(line);
280 |     if (addr) addresses.push(addr);
281 |   }
282 | 
283 |   return addresses;
284 | }
285 | ```
286 | 
287 | ## License
288 | 
289 | MIT License - see [LICENSE](LICENSE) file
290 | 
291 | ## Links
292 | 
293 | - **NPM Package**: https://www.npmjs.com/package/ipv6-parse
294 | - **GitHub**: https://github.com/jrepp/ipv6-parse
295 | - **Issues**: https://github.com/jrepp/ipv6-parse/issues
296 | - **WASM Documentation**: [README_WASM.md](README_WASM.md)
297 | - **Package Managers**: [README_PACKAGE_MANAGERS.md](README_PACKAGE_MANAGERS.md)
298 | 


--------------------------------------------------------------------------------
/README_CI.md:
--------------------------------------------------------------------------------
  1 | # Continuous Integration (CI) Guide
  2 | 
  3 | This document describes the CI/CD setup for the IPv6-parse project.
  4 | 
  5 | ## Overview
  6 | 
  7 | The project uses GitHub Actions for continuous integration with the following features:
  8 | 
  9 | 1. **Multi-platform builds** - Linux, macOS, Windows
 10 | 2. **Multiple compiler versions** - GCC 11-12, Clang 11-15, MSVC, MinGW
 11 | 3. **Code coverage analysis** - Using gcov/lcov
 12 | 4. **Memory leak detection** - Using Valgrind
 13 | 5. **Automated testing** - All test suites run on every commit
 14 | 
 15 | ## Workflows
 16 | 
 17 | ### Coverage & Memory Check Job
 18 | 
 19 | **Job**: `coverage`
 20 | **Runs on**: Ubuntu Latest
 21 | **Triggers**: Push, Pull Request, Release, Weekly Schedule
 22 | 
 23 | This job performs comprehensive quality checks:
 24 | 
 25 | #### Steps:
 26 | 
 27 | 1. **Install Dependencies**
 28 |    ```bash
 29 |    sudo apt-get install -y lcov valgrind
 30 |    ```
 31 | 
 32 | 2. **Build with Coverage**
 33 |    ```bash
 34 |    cmake -DENABLE_COVERAGE=1 -DIPV6_PARSE_LIBRARY_ONLY=OFF ..
 35 |    cmake --build . -j
 36 |    ```
 37 | 
 38 | 3. **Run All Tests**
 39 |    - Original test suite (315 tests)
 40 |    - Extended test suite (36 tests)
 41 |    - Fuzz tests
 42 | 
 43 | 4. **Generate Coverage Report**
 44 |    - Uses lcov to collect coverage data
 45 |    - Filters out system files
 46 |    - Uploads to Codecov
 47 | 
 48 | 5. **Memory Leak Detection**
 49 |    - Runs Valgrind on all test binaries
 50 |    - Checks for memory leaks
 51 |    - Verifies no invalid memory access
 52 |    - Uses suppression file for false positives
 53 | 
 54 | ### Multi-Platform Build Job
 55 | 
 56 | **Job**: `cpp_build`
 57 | **Runs on**: Ubuntu, macOS, Windows
 58 | **Matrix**: 11 different compiler/OS combinations
 59 | 
 60 | Ensures compatibility across:
 61 | - **Linux**: GCC 11-12, Clang 11-15
 62 | - **macOS**: Clang, AppleClang
 63 | - **Windows**: MSVC, MinGW-GCC
 64 | 
 65 | Each build:
 66 | 1. Checks out code
 67 | 2. Installs compiler-specific dependencies
 68 | 3. Configures with CMake
 69 | 4. Builds the project
 70 | 5. Runs tests (Linux/macOS only)
 71 | 
 72 | ## Badges
 73 | 
 74 | Add these badges to your README.md:
 75 | 
 76 | ```markdown
 77 | ![CI Build Status](https://github.com/jrepp/ipv6-parse/workflows/CI%20Build%20Tests/badge.svg)
 78 | [![codecov](https://codecov.io/gh/jrepp/ipv6-parse/branch/master/graph/badge.svg)](https://codecov.io/gh/jrepp/ipv6-parse)
 79 | ```
 80 | 
 81 | ## Coverage Reporting
 82 | 
 83 | ### Codecov Integration
 84 | 
 85 | Coverage reports are automatically uploaded to [Codecov](https://codecov.io) after each build.
 86 | 
 87 | Features:
 88 | - Line and branch coverage tracking
 89 | - Pull request comments with coverage diff
 90 | - Coverage trend graphs
 91 | - Configurable coverage thresholds
 92 | 
 93 | ### Local Coverage
 94 | 
 95 | To run coverage locally:
 96 | 
 97 | ```bash
 98 | # Using the provided script
 99 | ./run_coverage.sh
100 | 
101 | # Or manually
102 | mkdir build && cd build
103 | cmake -DENABLE_COVERAGE=1 -DIPV6_PARSE_LIBRARY_ONLY=OFF ..
104 | cmake --build . -j
105 | ctest
106 | lcov --capture --directory . --output-file coverage.info
107 | lcov --remove coverage.info '/usr/*' --output-file coverage.info
108 | genhtml coverage.info --output-directory coverage_html
109 | open coverage_html/index.html  # View in browser
110 | ```
111 | 
112 | ## Valgrind Memory Checking
113 | 
114 | ### What is Checked
115 | 
116 | Valgrind detects:
117 | - Memory leaks (allocated but not freed)
118 | - Use of uninitialized values
119 | - Invalid memory access
120 | - Double frees
121 | - Mismatched allocations (malloc/delete, new/free)
122 | 
123 | ### Running Valgrind Locally
124 | 
125 | ```bash
126 | # Basic check
127 | valgrind --leak-check=full ./build/bin/ipv6-test
128 | 
129 | # Detailed check (same as CI)
130 | valgrind --leak-check=full \
131 |          --show-leak-kinds=all \
132 |          --track-origins=yes \
133 |          --verbose \
134 |          --error-exitcode=1 \
135 |          --suppressions=valgrind.supp \
136 |          ./build/bin/ipv6-test
137 | ```
138 | 
139 | ### Suppression File
140 | 
141 | The `valgrind.supp` file contains suppressions for known false positives.
142 | 
143 | To add a new suppression:
144 | 
145 | 1. Run Valgrind and get the error
146 | 2. Generate suppression:
147 |    ```bash
148 |    valgrind --gen-suppressions=all ./build/bin/ipv6-test 2>&1 | grep -A 20 "insert_a_suppression_name_here"
149 |    ```
150 | 3. Copy the suppression block to `valgrind.supp`
151 | 4. Give it a descriptive name
152 | 
153 | ## Troubleshooting
154 | 
155 | ### Coverage Not Generated
156 | 
157 | **Problem**: No coverage data after running tests
158 | 
159 | **Solutions**:
160 | 1. Ensure built with `ENABLE_COVERAGE=1`
161 | 2. Check `.gcda` files exist: `find . -name "*.gcda"`
162 | 3. Verify tests actually ran: `echo $?` after test execution
163 | 4. Clean and rebuild: `rm -rf build && mkdir build`
164 | 
165 | ### Valgrind Errors
166 | 
167 | **Problem**: Valgrind reports leaks or errors
168 | 
169 | **Solutions**:
170 | 1. Check if it's a real leak or false positive
171 | 2. For false positives, add to `valgrind.supp`
172 | 3. For real leaks, fix the code and verify:
173 |    ```bash
174 |    valgrind --leak-check=full ./build/bin/ipv6-test
175 |    ```
176 | 
177 | ### CI Build Failures
178 | 
179 | **Problem**: CI passes locally but fails on GitHub
180 | 
181 | **Solutions**:
182 | 1. Check compiler versions match
183 | 2. Look at full CI logs for specific errors
184 | 3. Ensure dependencies are installed
185 | 4. Test in Docker with same OS/compiler:
186 |    ```bash
187 |    docker run -it ubuntu:latest bash
188 |    # Install dependencies and test
189 |    ```
190 | 
191 | ### macOS Specific Issues
192 | 
193 | **Problem**: Valgrind not available on macOS
194 | 
195 | **Note**: Valgrind has limited support on macOS. The CI only runs Valgrind on Linux.
196 | 
197 | For macOS memory checking, use:
198 | - **Instruments** (Xcode's Leaks tool)
199 | - **AddressSanitizer**: `cmake -DCMAKE_CXX_FLAGS="-fsanitize=address" ..`
200 | - **LeakSanitizer**: `cmake -DCMAKE_CXX_FLAGS="-fsanitize=leak" ..`
201 | 
202 | ## Adding New Compilers
203 | 
204 | To test with a new compiler version:
205 | 
206 | 1. Edit `.github/workflows/ci.yml`
207 | 2. Add to the matrix:
208 |    ```yaml
209 |    - os: ubuntu-latest
210 |      CC: gcc-13
211 |      CXX: g++-13
212 |      CMAKE_CMD: cmake ..
213 |      NAME: gcc-13
214 |    ```
215 | 3. Update dependencies step if needed
216 | 4. Commit and push
217 | 
218 | ## Performance Considerations
219 | 
220 | ### Build Times
221 | 
222 | Typical CI build times:
223 | - Coverage job: ~2-3 minutes
224 | - Linux builds: ~1-2 minutes each
225 | - macOS builds: ~2-3 minutes each
226 | - Windows builds: ~3-5 minutes each
227 | 
228 | Total CI time: ~15-20 minutes for all jobs
229 | 
230 | ### Optimization Tips
231 | 
232 | 1. **Use ccache**: Cache compiler outputs
233 |    ```yaml
234 |    - uses: actions/cache@v3
235 |      with:
236 |        path: ~/.ccache
237 |        key: ${{ runner.os }}-ccache-${{ github.sha }}
238 |    ```
239 | 
240 | 2. **Parallel builds**: Already using `cmake --build . -j`
241 | 
242 | 3. **Matrix limits**: Consider if all compiler versions are needed
243 | 
244 | ## Maintenance
245 | 
246 | ### Weekly Schedule
247 | 
248 | The CI runs weekly (Sundays at 3:30 AM UTC) to catch:
249 | - Dependency updates
250 | - Platform changes
251 | - Bitrot
252 | 
253 | ### Updates Needed
254 | 
255 | Periodically update:
256 | - Compiler versions in matrix
257 | - GitHub Actions versions (`actions/checkout@v3`, etc.)
258 | - OS versions (`ubuntu-latest`, etc.)
259 | - Dependencies (lcov, valgrind)
260 | 
261 | ### Monitoring
262 | 
263 | Check regularly:
264 | - [GitHub Actions](https://github.com/jrepp/ipv6-parse/actions)
265 | - [Codecov Dashboard](https://codecov.io/gh/jrepp/ipv6-parse)
266 | - Test failure trends
267 | - Coverage trends
268 | 
269 | ## Additional Resources
270 | 
271 | - [GitHub Actions Documentation](https://docs.github.com/en/actions)
272 | - [Codecov Documentation](https://docs.codecov.com/)
273 | - [Valgrind Manual](https://valgrind.org/docs/manual/manual.html)
274 | - [gcov Documentation](https://gcc.gnu.org/onlinedocs/gcc/Gcov.html)
275 | - [lcov Documentation](http://ltp.sourceforge.net/coverage/lcov.php)
276 | 
277 | ## Support
278 | 
279 | For CI issues:
280 | 1. Check the [Actions tab](https://github.com/jrepp/ipv6-parse/actions)
281 | 2. Review workflow logs
282 | 3. Open an issue with:
283 |    - Job name that failed
284 |    - Error message
285 |    - Link to failed run
286 |    - Steps to reproduce locally
287 | 


--------------------------------------------------------------------------------
/test/test-api.js:
--------------------------------------------------------------------------------
  1 | /**
  2 |  * JavaScript API layer validation tests
  3 |  * Tests the high-level API (IPv6Parser, IPv6Address classes)
  4 |  */
  5 | 
  6 | const fs = require('fs');
  7 | const path = require('path');
  8 | 
  9 | // Load WASM module and API
 10 | const createIPv6Module = require(path.join(__dirname, '../docs/ipv6-parse.js'));
 11 | 
 12 | // Load API layer code
 13 | const apiCode = fs.readFileSync(
 14 |   path.join(__dirname, '../docs/ipv6-parse-api.js'),
 15 |   'utf8'
 16 | );
 17 | 
 18 | async function runTests() {
 19 |   console.log('JavaScript API Layer Tests\n===========================\n');
 20 | 
 21 |   let passed = 0;
 22 |   let failed = 0;
 23 | 
 24 |   try {
 25 |     // Initialize WASM module
 26 |     console.log('Initializing WASM module and API layer...');
 27 |     const wasmModule = await createIPv6Module();
 28 | 
 29 |     // Execute API layer code in context
 30 |     const context = { module: { exports: {} }, Module: wasmModule };
 31 |     const apiFunc = new Function('module', 'Module', apiCode + '\nreturn module.exports;');
 32 |     const api = apiFunc(context.module, wasmModule);
 33 | 
 34 |     const { IPv6Parser, IPv6Address, IPv6ParseError } = api;
 35 |     const parser = new IPv6Parser(wasmModule);
 36 | 
 37 |     console.log('✓ API layer initialized\n');
 38 | 
 39 |     // Test 1: Check class exports
 40 |     console.log('Test 1: Checking exported classes...');
 41 |     if (typeof IPv6Parser === 'function' &&
 42 |         typeof IPv6Address === 'function' &&
 43 |         typeof IPv6ParseError === 'function') {
 44 |       console.log('✓ All classes exported correctly');
 45 |       passed++;
 46 |     } else {
 47 |       console.error('✗ Missing class exports');
 48 |       failed++;
 49 |     }
 50 | 
 51 |     // Test 2: Parse basic address
 52 |     console.log('\nTest 2: Testing parser.parse()...');
 53 |     try {
 54 |       const addr = parser.parse('2001:db8::1');
 55 |       if (addr.formatted === '2001:db8::1' &&
 56 |           addr.components[0] === 0x2001 &&
 57 |           addr.components[1] === 0x0db8) {
 58 |         console.log('✓ Basic parsing works');
 59 |         passed++;
 60 |       } else {
 61 |         console.error('✗ Parsed data mismatch');
 62 |         failed++;
 63 |       }
 64 |     } catch (err) {
 65 |       console.error('✗ Parse failed:', err.message);
 66 |       failed++;
 67 |     }
 68 | 
 69 |     // Test 3: Parse with port
 70 |     console.log('\nTest 3: Testing port parsing...');
 71 |     try {
 72 |       const addr = parser.parse('[::1]:8080');
 73 |       if (addr.port === 8080 && addr.hasPort === true) {
 74 |         console.log('✓ Port parsing works');
 75 |         passed++;
 76 |       } else {
 77 |         console.error('✗ Port data mismatch');
 78 |         failed++;
 79 |       }
 80 |     } catch (err) {
 81 |       console.error('✗ Port parse failed:', err.message);
 82 |       failed++;
 83 |     }
 84 | 
 85 |     // Test 4: Parse with CIDR
 86 |     console.log('\nTest 4: Testing CIDR mask parsing...');
 87 |     try {
 88 |       const addr = parser.parse('2001:db8::1/64');
 89 |       if (addr.mask === 64 && addr.hasMask === true) {
 90 |         console.log('✓ CIDR mask parsing works');
 91 |         passed++;
 92 |       } else {
 93 |         console.error('✗ CIDR mask mismatch');
 94 |         failed++;
 95 |       }
 96 |     } catch (err) {
 97 |       console.error('✗ CIDR parse failed:', err.message);
 98 |       failed++;
 99 |     }
100 | 
101 |     // Test 5: Parse with zone ID
102 |     console.log('\nTest 5: Testing zone ID parsing...');
103 |     try {
104 |       const addr = parser.parse('fe80::1%eth0');
105 |       if (addr.zone === 'eth0') {
106 |         console.log('✓ Zone ID parsing works');
107 |         passed++;
108 |       } else {
109 |         console.error('✗ Zone ID mismatch');
110 |         failed++;
111 |       }
112 |     } catch (err) {
113 |       console.error('✗ Zone parse failed:', err.message);
114 |       failed++;
115 |     }
116 | 
117 |     // Test 6: Error handling
118 |     console.log('\nTest 6: Testing error handling...');
119 |     try {
120 |       parser.parse('invalid-address');
121 |       console.error('✗ Should have thrown error');
122 |       failed++;
123 |     } catch (err) {
124 |       if (err instanceof IPv6ParseError) {
125 |         console.log('✓ Error handling works');
126 |         passed++;
127 |       } else {
128 |         console.error('✗ Wrong error type:', err.constructor.name);
129 |         failed++;
130 |       }
131 |     }
132 | 
133 |     // Test 7: tryParse (null on error)
134 |     console.log('\nTest 7: Testing tryParse()...');
135 |     const result = parser.tryParse('invalid-address');
136 |     if (result === null) {
137 |       console.log('✓ tryParse returns null on error');
138 |       passed++;
139 |     } else {
140 |       console.error('✗ tryParse should return null');
141 |       failed++;
142 |     }
143 | 
144 |     // Test 8: isValid
145 |     console.log('\nTest 8: Testing isValid()...');
146 |     const valid = parser.isValid('2001:db8::1');
147 |     const invalid = parser.isValid('invalid');
148 |     if (valid === true && invalid === false) {
149 |       console.log('✓ isValid works correctly');
150 |       passed++;
151 |     } else {
152 |       console.error('✗ isValid returned wrong values');
153 |       failed++;
154 |     }
155 | 
156 |     // Test 9: equals
157 |     console.log('\nTest 9: Testing equals()...');
158 |     const equal = parser.equals('::1', '0:0:0:0:0:0:0:1');
159 |     if (equal === true) {
160 |       console.log('✓ equals works correctly');
161 |       passed++;
162 |     } else {
163 |       console.error('✗ equals returned false for equal addresses');
164 |       failed++;
165 |     }
166 | 
167 |     // Test 10: equals with ignorePort
168 |     console.log('\nTest 10: Testing equals() with ignorePort...');
169 |     const equalIgnorePort = parser.equals('[::1]:80', '[::1]:443', { ignorePort: true });
170 |     if (equalIgnorePort === true) {
171 |       console.log('✓ equals with ignorePort works');
172 |       passed++;
173 |     } else {
174 |       console.error('✗ equals should ignore port');
175 |       failed++;
176 |     }
177 | 
178 |     // Test 11: IPv6Address methods
179 |     console.log('\nTest 11: Testing IPv6Address methods...');
180 |     try {
181 |       const addr = parser.parse('2001:db8::1');
182 |       const json = addr.toJSON();
183 |       const str = addr.toString();
184 |       const hex = addr.getComponentHex(0);
185 | 
186 |       if (typeof json === 'object' &&
187 |           str === '2001:db8::1' &&
188 |           hex === '2001') {
189 |         console.log('✓ IPv6Address methods work');
190 |         passed++;
191 |       } else {
192 |         console.error('✗ IPv6Address methods returned unexpected values');
193 |         failed++;
194 |       }
195 |     } catch (err) {
196 |       console.error('✗ IPv6Address methods failed:', err.message);
197 |       failed++;
198 |     }
199 | 
200 |     // Test 12: Immutability
201 |     console.log('\nTest 12: Testing IPv6Address immutability...');
202 |     try {
203 |       const addr = parser.parse('2001:db8::1');
204 |       const originalFormatted = addr.formatted;
205 | 
206 |       try {
207 |         addr.formatted = 'modified';
208 |       } catch (e) {
209 |         // Expected to fail in strict mode
210 |       }
211 | 
212 |       if (addr.formatted === originalFormatted) {
213 |         console.log('✓ IPv6Address is immutable');
214 |         passed++;
215 |       } else {
216 |         console.error('✗ IPv6Address should be immutable');
217 |         failed++;
218 |       }
219 |     } catch (err) {
220 |       console.error('✗ Immutability test failed:', err.message);
221 |       failed++;
222 |     }
223 | 
224 |     // Test 13: getVersion
225 |     console.log('\nTest 13: Testing getVersion()...');
226 |     try {
227 |       const version = parser.getVersion();
228 |       if (version && version.includes('wasm')) {
229 |         console.log(`✓ getVersion works: ${version}`);
230 |         passed++;
231 |       } else {
232 |         console.error('✗ getVersion returned unexpected value');
233 |         failed++;
234 |       }
235 |     } catch (err) {
236 |       console.error('✗ getVersion failed:', err.message);
237 |       failed++;
238 |     }
239 | 
240 |   } catch (err) {
241 |     console.error('Fatal error:', err);
242 |     process.exit(1);
243 |   }
244 | 
245 |   // Summary
246 |   console.log('\n===========================');
247 |   console.log(`Tests passed: ${passed}`);
248 |   console.log(`Tests failed: ${failed}`);
249 |   console.log('===========================\n');
250 | 
251 |   process.exit(failed > 0 ? 1 : 0);
252 | }
253 | 
254 | runTests().catch(err => {
255 |   console.error('Fatal error:', err);
256 |   process.exit(1);
257 | });
258 | 


--------------------------------------------------------------------------------
/ipv6_wasm.c:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright (c) 2017-2025 Jacob Repp 
  3 |  *
  4 |  * SPDX-License-Identifier: MIT
  5 |  */
  6 | 
  7 | /**
  8 |  * WebAssembly bindings for ipv6-parse library
  9 |  *
 10 |  * This file provides efficient, idiomatic JavaScript exports for the ipv6-parse
 11 |  * library. The API is designed to minimize WASM boundary crossings by returning
 12 |  * all parsed data in a single call via a packed structure.
 13 |  *
 14 |  * Design principles:
 15 |  * - No global state (all functions are stateless)
 16 |  * - Single call to get all data (pack into struct)
 17 |  * - JavaScript layer provides idiomatic API
 18 |  * - Cache-friendly data structures
 19 |  */
 20 | 
 21 | #include "ipv6.h"
 22 | #include 
 23 | #include 
 24 | 
 25 | #ifdef __EMSCRIPTEN__
 26 | #include 
 27 | #else
 28 | #define EMSCRIPTEN_KEEPALIVE
 29 | #endif
 30 | 
 31 | /**
 32 |  * Packed result structure for efficient data transfer to JavaScript
 33 |  *
 34 |  * Total size: 92 bytes (cache-line friendly)
 35 |  * Designed for single-copy read from JavaScript via heap access
 36 |  */
 37 | typedef struct {
 38 |     uint16_t components[8];  // 16 bytes - address components in network byte order
 39 |     uint16_t port;           // 2 bytes  - port number (0 if not specified)
 40 |     uint16_t pad0;           // 2 bytes  - alignment padding
 41 |     uint32_t mask;           // 4 bytes  - CIDR mask bits (0 if not specified)
 42 |     uint32_t flags;          // 4 bytes  - feature flags (IPV6_FLAG_*)
 43 |     char formatted[48];      // 48 bytes - RFC 5952 formatted address string
 44 |     char zone[16];           // 16 bytes - zone ID / interface name
 45 | } ipv6_parse_result_t;
 46 | 
 47 | /**
 48 |  * Diagnostic information structure for parse errors
 49 |  *
 50 |  * Total size: 144 bytes
 51 |  * Contains detailed error information when parsing fails
 52 |  */
 53 | typedef struct {
 54 |     uint32_t event;          // 4 bytes  - diagnostic event type (ipv6_diag_event_t)
 55 |     uint32_t position;       // 4 bytes  - position in input where error occurred
 56 |     char message[64];        // 64 bytes - error message
 57 |     char input[64];          // 64 bytes - input string that caused error
 58 |     uint32_t has_error;      // 4 bytes  - 1 if diagnostic was captured, 0 otherwise
 59 |     uint32_t pad0;           // 4 bytes  - alignment padding
 60 | } ipv6_diag_result_t;
 61 | 
 62 | /**
 63 |  * Parse an IPv6/IPv4 address and return all data in one call
 64 |  *
 65 |  * This is the primary API function. It parses the input address and populates
 66 |  * the result structure with all parsed data, minimizing WASM boundary crossings.
 67 |  *
 68 |  * @param input  - Input address string (e.g., "::1", "[::1]:8080", "fe80::1%eth0")
 69 |  * @param result - Pointer to result structure (allocated by JavaScript)
 70 |  * @return 1 on success, 0 on parse failure
 71 |  *
 72 |  * Example from JavaScript:
 73 |  *   const resultPtr = Module._malloc(80); // sizeof(ipv6_parse_result_t)
 74 |  *   const success = Module.ccall('ipv6_parse_full', 'number',
 75 |  *                                ['string', 'number'], [address, resultPtr]);
 76 |  */
 77 | EMSCRIPTEN_KEEPALIVE
 78 | int ipv6_parse_full(const char* input, ipv6_parse_result_t* result) {
 79 |     if (!input || !result) {
 80 |         return 0;
 81 |     }
 82 | 
 83 |     // Parse address using core library
 84 |     ipv6_address_full_t addr;
 85 |     memset(&addr, 0, sizeof(addr));
 86 |     memset(result, 0, sizeof(ipv6_parse_result_t));
 87 | 
 88 |     size_t len = strlen(input);
 89 |     if (!ipv6_from_str(input, len, &addr)) {
 90 |         return 0;  // Parse failed
 91 |     }
 92 | 
 93 |     // Pack all data into result structure for single-copy transfer
 94 |     memcpy(result->components, addr.address.components, sizeof(result->components));
 95 |     result->port = addr.port;
 96 |     result->mask = addr.mask;
 97 |     result->flags = addr.flags;
 98 | 
 99 |     // Format the address according to RFC 5952
100 |     ipv6_to_str(&addr, result->formatted, sizeof(result->formatted));
101 | 
102 |     // Copy zone ID if present
103 |     if (addr.iface_len > 0 && addr.iface_len < sizeof(result->zone)) {
104 |         memcpy(result->zone, addr.iface, addr.iface_len);
105 |         result->zone[addr.iface_len] = '\0';
106 |     }
107 | 
108 |     return 1;  // Success
109 | }
110 | 
111 | /**
112 |  * Compare two addresses for equality (stateless)
113 |  *
114 |  * @param addr1        - First address string
115 |  * @param addr2        - Second address string
116 |  * @param ignore_flags - Flags to ignore in comparison (bitwise OR of IPV6_FLAG_*)
117 |  * @return 0 if equal (IPV6_COMPARE_OK), non-zero otherwise
118 |  *
119 |  * Common ignore_flags:
120 |  *   0x01 - Ignore port (IPV6_FLAG_HAS_PORT)
121 |  *   0x02 - Ignore CIDR mask (IPV6_FLAG_HAS_MASK)
122 |  *   0x04 - Ignore IPv4 embed format (IPV6_FLAG_IPV4_EMBED)
123 |  */
124 | EMSCRIPTEN_KEEPALIVE
125 | int ipv6_compare_str(const char* addr1, const char* addr2, uint32_t ignore_flags) {
126 |     if (!addr1 || !addr2) {
127 |         return -1;  // Invalid input
128 |     }
129 | 
130 |     ipv6_address_full_t a1, a2;
131 |     memset(&a1, 0, sizeof(a1));
132 |     memset(&a2, 0, sizeof(a2));
133 | 
134 |     // Parse both addresses
135 |     if (!ipv6_from_str(addr1, strlen(addr1), &a1)) {
136 |         return -1;  // First address invalid
137 |     }
138 |     if (!ipv6_from_str(addr2, strlen(addr2), &a2)) {
139 |         return -1;  // Second address invalid
140 |     }
141 | 
142 |     // Use core library comparison
143 |     return ipv6_compare(&a1, &a2, ignore_flags);
144 | }
145 | 
146 | /**
147 |  * Get library version string
148 |  *
149 |  * @return Version string (e.g., "1.2.1-wasm")
150 |  */
151 | EMSCRIPTEN_KEEPALIVE
152 | const char* ipv6_version() {
153 |     return "1.2.1-wasm";
154 | }
155 | 
156 | /**
157 |  * Get size of result structure
158 |  *
159 |  * Helper for JavaScript to allocate the correct amount of memory.
160 |  * @return sizeof(ipv6_parse_result_t) in bytes
161 |  */
162 | EMSCRIPTEN_KEEPALIVE
163 | int ipv6_result_size() {
164 |     return sizeof(ipv6_parse_result_t);
165 | }
166 | 
167 | /**
168 |  * Get size of diagnostic result structure
169 |  *
170 |  * Helper for JavaScript to allocate the correct amount of memory.
171 |  * @return sizeof(ipv6_diag_result_t) in bytes
172 |  */
173 | EMSCRIPTEN_KEEPALIVE
174 | int ipv6_diag_size() {
175 |     return sizeof(ipv6_diag_result_t);
176 | }
177 | 
178 | /**
179 |  * Diagnostic callback function to capture parse errors
180 |  */
181 | static void diag_callback(ipv6_diag_event_t event, const ipv6_diag_info_t* info, void* user_data) {
182 |     ipv6_diag_result_t* diag = (ipv6_diag_result_t*)user_data;
183 |     if (!diag || !info) {
184 |         return;
185 |     }
186 | 
187 |     // Capture first error only
188 |     if (diag->has_error) {
189 |         return;
190 |     }
191 | 
192 |     diag->event = (uint32_t)event;
193 |     diag->position = info->position;
194 |     diag->has_error = 1;
195 | 
196 |     // Copy message (truncate if necessary)
197 |     if (info->message) {
198 |         size_t len = strlen(info->message);
199 |         if (len >= sizeof(diag->message)) {
200 |             len = sizeof(diag->message) - 1;
201 |         }
202 |         memcpy(diag->message, info->message, len);
203 |         diag->message[len] = '\0';
204 |     }
205 | 
206 |     // Copy input (truncate if necessary)
207 |     if (info->input) {
208 |         size_t len = strlen(info->input);
209 |         if (len >= sizeof(diag->input)) {
210 |             len = sizeof(diag->input) - 1;
211 |         }
212 |         memcpy(diag->input, info->input, len);
213 |         diag->input[len] = '\0';
214 |     }
215 | }
216 | 
217 | /**
218 |  * Parse an IPv6/IPv4 address with diagnostic output
219 |  *
220 |  * This function is similar to ipv6_parse_full but provides detailed diagnostic
221 |  * information when parsing fails.
222 |  *
223 |  * @param input  - Input address string
224 |  * @param result - Pointer to result structure (allocated by JavaScript)
225 |  * @param diag   - Pointer to diagnostic structure (allocated by JavaScript)
226 |  * @return 1 on success, 0 on parse failure (with diag populated)
227 |  *
228 |  * Example from JavaScript:
229 |  *   const resultPtr = Module._malloc(92);
230 |  *   const diagPtr = Module._malloc(144);
231 |  *   const success = Module.ccall('ipv6_parse_full_diag', 'number',
232 |  *                                ['string', 'number', 'number'],
233 |  *                                [address, resultPtr, diagPtr]);
234 |  */
235 | EMSCRIPTEN_KEEPALIVE
236 | int ipv6_parse_full_diag(const char* input, ipv6_parse_result_t* result, ipv6_diag_result_t* diag) {
237 |     if (!input || !result || !diag) {
238 |         return 0;
239 |     }
240 | 
241 |     // Initialize diagnostic structure
242 |     memset(diag, 0, sizeof(ipv6_diag_result_t));
243 | 
244 |     // Parse address using diagnostic version
245 |     ipv6_address_full_t addr;
246 |     memset(&addr, 0, sizeof(addr));
247 |     memset(result, 0, sizeof(ipv6_parse_result_t));
248 | 
249 |     size_t len = strlen(input);
250 |     if (!ipv6_from_str_diag(input, len, &addr, diag_callback, diag)) {
251 |         // Parse failed - diagnostic info should be populated
252 |         return 0;
253 |     }
254 | 
255 |     // Pack all data into result structure for single-copy transfer
256 |     memcpy(result->components, addr.address.components, sizeof(result->components));
257 |     result->port = addr.port;
258 |     result->mask = addr.mask;
259 |     result->flags = addr.flags;
260 | 
261 |     // Format the address according to RFC 5952
262 |     ipv6_to_str(&addr, result->formatted, sizeof(result->formatted));
263 | 
264 |     // Copy zone ID if present
265 |     if (addr.iface_len > 0 && addr.iface_len < sizeof(result->zone)) {
266 |         memcpy(result->zone, addr.iface, addr.iface_len);
267 |         result->zone[addr.iface_len] = '\0';
268 |     }
269 | 
270 |     return 1;  // Success
271 | }
272 | 


--------------------------------------------------------------------------------
/ipv6.h:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Copyright (c) 2017-2025 Jacob Repp 
  3 |  *
  4 |  * SPDX-License-Identifier: MIT
  5 |  *
  6 |  * Permission is hereby granted, free of charge, to any person obtaining a copy
  7 |  * of this software and associated documentation files (the "Software"), to deal
  8 |  * in the Software without restriction, including without limitation the rights
  9 |  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 10 |  * copies of the Software, and to permit persons to whom the Software is
 11 |  * furnished to do so, subject to the following conditions:
 12 |  *
 13 |  * The above copyright notice and this permission notice shall be included in all
 14 |  * copies or substantial portions of the Software.
 15 |  *
 16 |  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 17 |  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 18 |  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 19 |  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 20 |  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 21 |  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 22 |  * SOFTWARE.
 23 |  */
 24 | 
 25 | #pragma once
 26 | // # IPv6 / IPv4 address parser in C
 27 | //
 28 | //     A self-contained embeddable address parsing library.
 29 | //
 30 | // ![IPv6 Parse Diagram](ipv6-parse.png)
 31 | //
 32 | // Author: jacobrepp@gmail.com
 33 | // License: MIT
 34 | //
 35 | // [![Build Status](https://travis-ci.org/jrepp/ipv6-parse.svg?branch=master)](https://travis-ci.org/jrepp/ipv6-parse)
 36 | //
 37 | // ## RFC Conformance
 38 | //
 39 | // This library implements the following IETF RFCs:
 40 | // - RFC 4291: IPv6 Addressing Architecture
 41 | // - RFC 5952: IPv6 Address Text Representation
 42 | // - RFC 4007: IPv6 Scoped Address Architecture (Zone IDs)
 43 | //
 44 | // ## Features
 45 | //
 46 | // - Single header, multi-platform
 47 | // - Full support for the IPv6 & IPv4 addresses, including CIDR
 48 | //   - Abbreviations `::1`, `ff::1:2`
 49 | //   - Embedded IPv4 `ffff::10.11.82.1`
 50 | //   - CIDR notation `ffff::/80`
 51 | //   - Port notation `[::1]:1119`
 52 | //   - IPv4 `10.11.82.1`, `10.11.82.1:5555`, `10.0.0.0/8`
 53 | //   - IPv4 addresses with less than 4 octets (1 -> 0.0.0.1, 1.2 -> 1.0.0.2, 1.2.3 -> 1.2.0.3)
 54 | //   - Basic IPv4 with port 10.1:8080 -> 10.0.0.1:8080
 55 | //   - Combinations of the above `[ffff::1.2.3.4/128]:1119`
 56 | // - Single function to parse both IPv4 and IPv6 addresses and ports
 57 | // - Rich diagnostic information regarding addresses formatting
 58 | // - Two way functionality address -> parse -> string -> parse
 59 | // - Careful use of strings and pointers
 60 | // - Comprehensive tests
 61 | //
 62 | //
 63 | // ## IPv4 Compatibility Mode
 64 | //
 65 | // See test.c: test_api_use_loopback_const
 66 | // ```c
 67 | //     127.111.2.1
 68 | //     uint16_t components[IPV6_NUM_COMPONENTS] = {
 69 | //         0x7f6f,
 70 | //         0x0201 }
 71 | // ```
 72 | //
 73 | // - Addresses can be constructed directly in code and support the full two-way functionality
 74 | //
 75 | // ## Building / Debugging
 76 | //
 77 | // Full tracing can be enabled by running `cmake -DPARSE_TRACE=1`
 78 | //
 79 | 
 80 | #include 
 81 | #include 
 82 | #include 
 83 | 
 84 | #ifdef __cplusplus
 85 | extern "C" {
 86 | #endif
 87 | 
 88 | 
 89 | // These constants are for self-documenting string formatting expansions
 90 | // sizeof "[1234:1234:1234:1234:1234:1234:1234:1234/128%longinterface]:65535";
 91 | #define IPV6_STRING_SIZE 66
 92 | 
 93 | // sizeof "255.255.255.255:65535";
 94 | #define IPV4_STRING_SIZE 22
 95 | 
 96 | // ### ipv6_flag_t
 97 | //
 98 | // Flags are used to communicate which fields are filled out in the address structure
 99 | // after parsing. Address components are assumed.
100 | //
101 | // ~~~~
102 | typedef enum {
103 |     IPV6_FLAG_HAS_PORT      = 0x00000001,   // the address specifies a port setting
104 |     IPV6_FLAG_HAS_MASK      = 0x00000002,   // the address specifies a CIDR mask
105 |     IPV6_FLAG_IPV4_EMBED    = 0x00000004,   // the address has an embedded IPv4 address in the last 32bits
106 |     IPV6_FLAG_IPV4_COMPAT   = 0x00000008,   // the address is IPv4 compatible (1.2.3.4:5555)
107 | } ipv6_flag_t;
108 | // ~~~~
109 | 
110 | // ### ipv6_address_t
111 | //
112 | // Simplified address structure where the components are represented in
113 | // machine format, left to right [0..7]
114 | //
115 | // e.g. little-endian x86 machines:
116 | //
117 | //      aa11:bb22:: -> 0xaa11, 0xbb22, 0x0000, ...
118 | //
119 | // ~~~~
120 | #define IPV6_NUM_COMPONENTS 8
121 | #define IPV4_NUM_COMPONENTS 2
122 | #define IPV4_EMBED_INDEX 6
123 | typedef struct {
124 |     uint16_t                components[IPV6_NUM_COMPONENTS];
125 | } ipv6_address_t;
126 | // ~~~~
127 | 
128 | 
129 | // *ipv6_address_full_t*
130 | //
131 | // Input / output type for round trip parsing and converting to string.
132 | //
133 | // Features are indicated using flags, see *ipv6_flag_t*.
134 | //
135 | // ~~~~
136 | typedef struct {
137 |     ipv6_address_t          address;        // address components
138 |     uint16_t                port;           // port binding
139 |     uint16_t                pad0;           // first padding
140 |     uint32_t                mask;           // number of mask bits N specified for example in ::1/N
141 |     const char*             iface;          // pointer to place in address string where interface is defined
142 |     uint32_t                iface_len;      // number of bytes in the name of the interface
143 |     uint32_t                flags;          // flags indicating features of address
144 | } ipv6_address_full_t;
145 | // ~~~~
146 | 
147 | 
148 | // ### ipv6_compare_t
149 | //
150 | // Result of ipv6_compare of two addresses
151 | //
152 | // ~~~~
153 | typedef enum {
154 |     IPV6_COMPARE_OK = 0,
155 |     IPV6_COMPARE_FORMAT_MISMATCH,       // address differ in their
156 |     IPV6_COMPARE_MASK_MISMATCH,         // the CIDR mask does not match
157 |     IPV6_COMPARE_PORT_MISMATCH,         // the port does not match
158 |     IPV6_COMPARE_ADDRESS_MISMATCH,      // address components do not match
159 | } ipv6_compare_result_t;
160 | // ~~~~
161 | 
162 | // ### ipv6_diag_event_t
163 | //
164 | // Event type emitted from diagnostic function
165 | //
166 | // ~~~~
167 | typedef enum {
168 |     IPV6_DIAG_STRING_SIZE_EXCEEDED          = 0,
169 |     IPV6_DIAG_INVALID_INPUT                 = 1,
170 |     IPV6_DIAG_INVALID_INPUT_CHAR            = 2,
171 |     IPV6_DIAG_TRAILING_ZEROES               = 3,
172 |     IPV6_DIAG_V6_BAD_COMPONENT_COUNT        = 4,
173 |     IPV6_DIAG_V4_BAD_COMPONENT_COUNT        = 5,
174 |     IPV6_DIAG_V6_COMPONENT_OUT_OF_RANGE     = 6,
175 |     IPV6_DIAG_V4_COMPONENT_OUT_OF_RANGE     = 7,
176 |     IPV6_DIAG_INVALID_PORT                  = 8,
177 |     IPV6_DIAG_INVALID_CIDR_MASK             = 9,
178 |     IPV6_DIAG_IPV4_REQUIRED_BITS            = 10,
179 |     IPV6_DIAG_IPV4_INCORRECT_POSITION       = 11,
180 |     IPV6_DIAG_INVALID_BRACKETS              = 12,
181 |     IPV6_DIAG_INVALID_ABBREV                = 13,
182 |     IPV6_DIAG_INVALID_DECIMAL_TOKEN         = 14,
183 |     IPV6_DIAG_INVALID_HEX_TOKEN             = 15,
184 | } ipv6_diag_event_t;
185 | // ~~~~
186 | 
187 | 
188 | // ### ipv6_diag_info_t
189 | //
190 | // Structure that carriers information about the diagnostic message
191 | //
192 | // ~~~~
193 | typedef struct {
194 |     const char* message;    // English ascii debug message
195 |     const char* input;      // Input string that generated the diagnostic
196 |     uint32_t    position;   // Position in input that caused the diagnostic
197 |     uint32_t    pad0;
198 | } ipv6_diag_info_t;
199 | // ~~~~
200 | 
201 | 
202 | /// These macros define the signature type of the API functions
203 | #define IPV6_API_DECL(name) name
204 | #define IPV6_API_DEF(name) name
205 | 
206 | // ### ipv6_diag_func_t
207 | //
208 | // A diagnostic function that receives information from parsing the address
209 | //
210 | // ~~~~
211 | typedef void (*ipv6_diag_func_t) (
212 |     ipv6_diag_event_t event,
213 |     const ipv6_diag_info_t* info,
214 |     void* user_data);
215 | // ~~~~
216 | 
217 | // ### ipv6_from_str
218 | //
219 | // Read an IPv6 address from a string, handles parsing a variety of format
220 | // information from the spec. Will also handle IPv4 address passed in without
221 | // any embedding information.
222 | //
223 | // ~~~~
224 | bool IPV6_API_DECL(ipv6_from_str) (
225 |     const char* input,
226 |     size_t input_bytes,
227 |     ipv6_address_full_t* out);
228 | // ~~~~
229 | 
230 | 
231 | // ### ipv6_from_str_diag
232 | //
233 | // Additional functionality parser that receives diagnostics information from parsing the address,
234 | // including errors.
235 | //
236 | // ~~~~
237 | bool IPV6_API_DECL(ipv6_from_str_diag) (
238 |     const char* input,
239 |     size_t input_bytes,
240 |     ipv6_address_full_t* out,
241 |     ipv6_diag_func_t func,
242 |     void* user_data);
243 | // ~~~~
244 | 
245 | // ### ipv6_to_str
246 | //
247 | // Convert an IPv6 structure to an ASCII string.
248 | //
249 | // The conversion will flatten zero address components according to the address
250 | // formatting specification. For example: ffff:0:0:0:0:0:0:1 -> ffff::1
251 | //
252 | // Requires output_bytes
253 | // Returns the size in bytes of the string minus the nul byte.
254 | //
255 | // ~~~~
256 | size_t IPV6_API_DECL(ipv6_to_str) (
257 |     const ipv6_address_full_t* in,
258 |     char* output,
259 |     size_t output_bytes);
260 | // ~~~~
261 | 
262 | 
263 | // ### ipv6_compare
264 | //
265 | // Compare two addresses, 0 (IPV6_COMPARE_OK) if equal, else ipv6_compare_result_t.
266 | //
267 | // Use IPV6_FLAG_HAS_MASK, IPV6_FLAG_HAS_PORT in ignore_flags to
268 | // ignore mask or port in comparisons.
269 | //
270 | // IPv4 embed and IPv4 compatible addresses will be compared as
271 | // equal if either IPV6_FLAG_IPV4_EMBED or IPV6_FLAG_IPV4_COMPAT
272 | // flags are passed in ignore_flags.
273 | //
274 | // ~~~~
275 | ipv6_compare_result_t IPV6_API_DECL(ipv6_compare) (
276 |     const ipv6_address_full_t* a,
277 |     const ipv6_address_full_t* b,
278 |     uint32_t ignore_flags);
279 | // ~~~~
280 | 
281 | #ifdef __cplusplus
282 | } // extern "C"
283 | #endif
284 | 


--------------------------------------------------------------------------------
/test/test-performance.js:
--------------------------------------------------------------------------------
  1 | /**
  2 |  * WASM Performance Benchmarks
  3 |  *
  4 |  * Measures the performance of the single-call WASM API design
  5 |  * and validates the 2-3x performance claim vs naive multi-call approach.
  6 |  */
  7 | 
  8 | const path = require('path');
  9 | 
 10 | // Load WASM module and API
 11 | const createIPv6Module = require(path.join(__dirname, '../docs/ipv6-parse.js'));
 12 | const { IPv6Parser } = require(path.join(__dirname, '../docs/ipv6-parse-api.js'));
 13 | 
 14 | // Test addresses covering various complexity levels
 15 | const testAddresses = [
 16 |   // Simple addresses
 17 |   '::1',
 18 |   '2001:db8::1',
 19 |   'fe80::1',
 20 | 
 21 |   // With CIDR
 22 |   '2001:db8::/32',
 23 |   '::1/128',
 24 |   'fe80::/10',
 25 | 
 26 |   // With port
 27 |   '[::1]:8080',
 28 |   '[2001:db8::1]:443',
 29 |   '[fe80::1]:22',
 30 | 
 31 |   // With zone ID
 32 |   'fe80::1%eth0',
 33 |   'fe80::1%en0',
 34 |   'fe80::1%lo0',
 35 | 
 36 |   // IPv4 embedded
 37 |   '::ffff:192.0.2.1',
 38 |   '::ffff:10.0.0.1',
 39 |   '::ffff:172.16.0.1',
 40 | 
 41 |   // Complex combinations
 42 |   '[2001:db8::1/64]:443',
 43 |   '[fe80::1%eth0]:8080',
 44 |   '[2001:db8::1/48%eth0]:443',
 45 | 
 46 |   // IPv4 addresses
 47 |   '192.168.1.1',
 48 |   '10.0.0.1:8080',
 49 |   '172.16.0.1/24'
 50 | ];
 51 | 
 52 | /**
 53 |  * Format a large number with thousand separators
 54 |  */
 55 | function formatNumber(num) {
 56 |   return num.toLocaleString('en-US', { maximumFractionDigits: 0 });
 57 | }
 58 | 
 59 | /**
 60 |  * Format nanoseconds to human-readable time
 61 |  */
 62 | function formatTime(ns) {
 63 |   if (ns < 1000) return `${ns.toFixed(2)} ns`;
 64 |   if (ns < 1000000) return `${(ns / 1000).toFixed(2)} μs`;
 65 |   return `${(ns / 1000000).toFixed(2)} ms`;
 66 | }
 67 | 
 68 | /**
 69 |  * Simulate naive multi-call approach (for comparison)
 70 |  * This is what users would have to do without the single-call API
 71 |  */
 72 | function simulateNaiveApproach(_module, _address) {
 73 |   // Each property access would be a separate WASM call
 74 |   const calls = [
 75 |     'ipv6_parse_full',      // Parse
 76 |     'wasm_get_formatted',   // Get formatted string
 77 |     'wasm_get_port',        // Get port
 78 |     'wasm_get_mask',        // Get mask
 79 |     'wasm_get_zone',        // Get zone
 80 |     'wasm_get_component_0', // Get component 0
 81 |     'wasm_get_component_1', // Get component 1
 82 |     'wasm_get_component_2', // Get component 2
 83 |     'wasm_get_component_3', // Get component 3
 84 |     'wasm_get_component_4', // Get component 4
 85 |     'wasm_get_component_5', // Get component 5
 86 |     'wasm_get_component_6', // Get component 6
 87 |     'wasm_get_component_7', // Get component 7
 88 |     'wasm_get_flags'       // Get flags
 89 |   ];
 90 | 
 91 |   // Simulate overhead: 14 boundary crossings + JS object construction
 92 |   // Each boundary crossing has overhead (parameter marshalling, context switching)
 93 |   return calls.length; // Return call count for calculation
 94 | }
 95 | 
 96 | /**
 97 |  * Run performance benchmark
 98 |  */
 99 | async function runBenchmark() {
100 |   console.log('WASM Performance Benchmarks\n============================\n');
101 | 
102 |   try {
103 |     // Initialize WASM module
104 |     console.log('Initializing WASM module...');
105 |     const module = await createIPv6Module();
106 |     const parser = new IPv6Parser(module);
107 |     console.log('✓ WASM module initialized\n');
108 | 
109 |     // Warm up (ensure JIT compilation, cache warming, etc.)
110 |     console.log('Warming up...');
111 |     for (let i = 0; i < 1000; i++) {
112 |       parser.parse('2001:db8::1');
113 |     }
114 |     console.log('✓ Warm-up complete\n');
115 | 
116 |     // === Test 1: Single-call API throughput ===
117 |     console.log('Test 1: Single-call API Throughput\n-----------------------------------');
118 |     const iterations = 100000;
119 | 
120 |     const startTime = Date.now();
121 |     let successCount = 0;
122 | 
123 |     for (let i = 0; i < iterations; i++) {
124 |       const addr = testAddresses[i % testAddresses.length];
125 |       const result = parser.parse(addr);
126 |       if (result) successCount++;
127 |     }
128 | 
129 |     const endTime = Date.now();
130 |     const duration = endTime - startTime;
131 |     const throughput = iterations / (duration / 1000);
132 |     const avgLatency = (duration * 1000000) / iterations; // Convert to nanoseconds
133 | 
134 |     console.log(`Iterations:     ${formatNumber(iterations)}`);
135 |     console.log(`Duration:       ${duration} ms`);
136 |     console.log(`Success rate:   ${((successCount / iterations) * 100).toFixed(2)}%`);
137 |     console.log(`Throughput:     ${formatNumber(throughput)} parses/sec`);
138 |     console.log(`Avg latency:    ${formatTime(avgLatency)}`);
139 |     console.log();
140 | 
141 |     // === Test 2: Per-address-type breakdown ===
142 |     console.log('Test 2: Performance by Address Type\n------------------------------------');
143 | 
144 |     const addressTypes = {
145 |       'Simple IPv6': ['::1', '2001:db8::1', 'fe80::1'],
146 |       'With CIDR': ['2001:db8::/32', '::1/128', 'fe80::/10'],
147 |       'With port': ['[::1]:8080', '[2001:db8::1]:443', '[fe80::1]:22'],
148 |       'With zone': ['fe80::1%eth0', 'fe80::1%en0', 'fe80::1%lo0'],
149 |       'IPv4 embedded': ['::ffff:192.0.2.1', '::ffff:10.0.0.1', '::ffff:172.16.0.1'],
150 |       'Complex': ['[2001:db8::1/64]:443', '[fe80::1%eth0]:8080', '[2001:db8::1/48%eth0]:443'],
151 |       'IPv4': ['192.168.1.1', '10.0.0.1:8080', '172.16.0.1/24']
152 |     };
153 | 
154 |     const typeResults = {};
155 |     const perTypeIterations = 10000;
156 | 
157 |     for (const [type, addresses] of Object.entries(addressTypes)) {
158 |       const typeStart = Date.now();
159 | 
160 |       for (let i = 0; i < perTypeIterations; i++) {
161 |         const addr = addresses[i % addresses.length];
162 |         parser.parse(addr);
163 |       }
164 | 
165 |       const typeEnd = Date.now();
166 |       const typeDuration = typeEnd - typeStart;
167 |       const typeThroughput = perTypeIterations / (typeDuration / 1000);
168 |       const typeLatency = (typeDuration * 1000000) / perTypeIterations;
169 | 
170 |       typeResults[type] = {
171 |         throughput: typeThroughput,
172 |         latency: typeLatency
173 |       };
174 | 
175 |       console.log(`${type.padEnd(20)} ${formatNumber(typeThroughput).padStart(12)} parses/sec  ${formatTime(typeLatency).padStart(12)}`);
176 |     }
177 |     console.log();
178 | 
179 |     // === Test 3: Single-call vs Naive comparison ===
180 |     console.log('Test 3: Single-call API vs Naive Multi-call\n--------------------------------------------');
181 | 
182 |     // Single-call: 1 WASM call per parse
183 |     const singleCallOverhead = 1;
184 | 
185 |     // Naive: 14 WASM calls per parse (simulated)
186 |     const naiveCallCount = simulateNaiveApproach(module, '2001:db8::1');
187 | 
188 |     // Estimate: Each boundary crossing adds ~50-100ns overhead
189 |     // Plus JavaScript object construction overhead
190 |     const boundaryOverheadNs = 75; // Conservative estimate
191 | 
192 |     const singleCallTotalNs = avgLatency;
193 |     const naiveEstimatedNs = (avgLatency - boundaryOverheadNs) + (boundaryOverheadNs * naiveCallCount);
194 | 
195 |     const speedup = naiveEstimatedNs / singleCallTotalNs;
196 | 
197 |     console.log('Single-call API:');
198 |     console.log(`  WASM calls:       ${singleCallOverhead} per parse`);
199 |     console.log(`  Avg latency:      ${formatTime(singleCallTotalNs)}`);
200 |     console.log(`  Throughput:       ${formatNumber(throughput)} parses/sec`);
201 |     console.log();
202 |     console.log('Naive multi-call (estimated):');
203 |     console.log(`  WASM calls:       ${naiveCallCount} per parse`);
204 |     console.log(`  Estimated latency: ${formatTime(naiveEstimatedNs)}`);
205 |     console.log(`  Estimated throughput: ${formatNumber(iterations / (naiveEstimatedNs / 1000000000 * iterations))} parses/sec`);
206 |     console.log();
207 |     console.log(`Speedup:            ${speedup.toFixed(2)}x faster`);
208 |     console.log(`Overhead reduction: ${((1 - singleCallOverhead / naiveCallCount) * 100).toFixed(1)}% fewer WASM calls`);
209 |     console.log();
210 | 
211 |     // === Test 4: Memory efficiency ===
212 |     console.log('Test 4: Memory Efficiency\n-------------------------');
213 | 
214 |     // Single-call: One 92-byte allocation, reused
215 |     const singleCallMemory = 92;
216 | 
217 |     // Naive: Would need temporary storage for each field
218 |     const naiveMemory = 92 + (14 * 8); // Struct + JS variables for each call
219 | 
220 |     console.log(`Single-call API:    ${singleCallMemory} bytes (one allocation, reused)`);
221 |     console.log(`Naive approach:     ${naiveMemory}+ bytes (multiple allocations per parse)`);
222 |     console.log(`Memory savings:     ${((1 - singleCallMemory / naiveMemory) * 100).toFixed(1)}% less memory per parse`);
223 |     console.log();
224 | 
225 |     // === Summary ===
226 |     console.log('Summary\n=======');
227 |     console.log(`✓ Single-call API achieves ${formatNumber(throughput)} parses/second`);
228 |     console.log(`✓ Average latency: ${formatTime(avgLatency)}`);
229 |     console.log(`✓ ${speedup.toFixed(2)}x faster than naive multi-call approach`);
230 |     console.log(`✓ ${((1 - singleCallOverhead / naiveCallCount) * 100).toFixed(0)}% reduction in WASM boundary crossings`);
231 |     console.log(`✓ ${((1 - singleCallMemory / naiveMemory) * 100).toFixed(0)}% memory savings per parse`);
232 |     console.log();
233 | 
234 |     // Verify performance claim
235 |     if (speedup >= 2.0 && speedup <= 3.5) {
236 |       console.log('✓ Performance meets 2-3x speedup claim!');
237 |       console.log();
238 |       return true;
239 |     } else if (speedup >= 1.5) {
240 |       console.log(`⚠ Performance is ${speedup.toFixed(2)}x (slightly below 2x claim, but still good)`);
241 |       console.log();
242 |       return true;
243 |     } else {
244 |       console.log(`✗ Performance is only ${speedup.toFixed(2)}x (below expected 2-3x)`);
245 |       console.log();
246 |       return false;
247 |     }
248 | 
249 |   } catch (err) {
250 |     console.error('Fatal error:', err);
251 |     return false;
252 |   }
253 | }
254 | 
255 | // Run benchmark if executed directly
256 | if (require.main === module) {
257 |   runBenchmark()
258 |     .then(success => {
259 |       process.exit(success ? 0 : 1);
260 |     })
261 |     .catch(err => {
262 |       console.error('Fatal error:', err);
263 |       process.exit(1);
264 |     });
265 | }
266 | 
267 | module.exports = { runBenchmark };
268 | 


--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
  1 | name: Release
  2 | 
  3 | on:
  4 |   push:
  5 |     tags:
  6 |       - 'v*.*.*'
  7 |   workflow_dispatch:
  8 |     inputs:
  9 |       tag:
 10 |         description: 'Tag to release'
 11 |         required: true
 12 |         type: string
 13 | 
 14 | permissions:
 15 |   contents: write
 16 |   pages: write
 17 |   id-token: write
 18 | 
 19 | jobs:
 20 |   build-matrix:
 21 |     name: Build ${{ matrix.os }} (${{ matrix.compiler }})
 22 |     runs-on: ${{ matrix.os }}
 23 |     strategy:
 24 |       fail-fast: false
 25 |       matrix:
 26 |         include:
 27 |           # Linux builds
 28 |           - os: ubuntu-22.04
 29 |             compiler: gcc
 30 |             build_type: Release
 31 |           - os: ubuntu-24.04
 32 |             compiler: gcc
 33 |             build_type: Release
 34 |           - os: ubuntu-24.04
 35 |             compiler: clang
 36 |             build_type: Release
 37 | 
 38 |           # macOS builds
 39 |           - os: macos-latest
 40 |             compiler: clang
 41 |             build_type: Release
 42 | 
 43 |           # Windows builds
 44 |           - os: windows-latest
 45 |             compiler: msvc
 46 |             build_type: Release
 47 | 
 48 |     steps:
 49 |       - name: Checkout code
 50 |         uses: actions/checkout@v4
 51 | 
 52 |       - name: Set up compiler (Linux)
 53 |         if: runner.os == 'Linux'
 54 |         run: |
 55 |           if [ "${{ matrix.compiler }}" = "clang" ]; then
 56 |             sudo apt-get update
 57 |             sudo apt-get install -y clang
 58 |             echo "CC=clang" >> $GITHUB_ENV
 59 |             echo "CXX=clang++" >> $GITHUB_ENV
 60 |           else
 61 |             echo "CC=gcc" >> $GITHUB_ENV
 62 |             echo "CXX=g++" >> $GITHUB_ENV
 63 |           fi
 64 | 
 65 |       - name: Configure CMake
 66 |         run: cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DBUILD_SHARED_LIBS=ON -DENABLE_COVERAGE=OFF
 67 | 
 68 |       - name: Build
 69 |         run: cmake --build build --config ${{ matrix.build_type }}
 70 | 
 71 |       - name: Test
 72 |         run: ctest --test-dir build --build-config ${{ matrix.build_type }} --output-on-failure
 73 | 
 74 |       - name: Package (Linux)
 75 |         if: runner.os == 'Linux' && matrix.compiler == 'gcc'
 76 |         run: |
 77 |           cd build
 78 |           cpack -G "DEB;RPM;TGZ"
 79 | 
 80 |       - name: Upload artifacts (Linux packages)
 81 |         if: runner.os == 'Linux' && matrix.compiler == 'gcc'
 82 |         uses: actions/upload-artifact@v4
 83 |         with:
 84 |           name: linux-packages-${{ matrix.os }}
 85 |           path: |
 86 |             build/*.deb
 87 |             build/*.rpm
 88 |             build/*.tar.gz
 89 | 
 90 |       - name: Upload artifacts (libraries)
 91 |         uses: actions/upload-artifact@v4
 92 |         with:
 93 |           name: libraries-${{ matrix.os }}-${{ matrix.compiler }}
 94 |           path: |
 95 |             build/bin/*
 96 | 
 97 |   build-wasm:
 98 |     name: Build WebAssembly
 99 |     runs-on: ubuntu-latest
100 |     steps:
101 |       - name: Checkout code
102 |         uses: actions/checkout@v4
103 | 
104 |       - name: Setup Emscripten
105 |         uses: mymindstorm/setup-emsdk@v14
106 |         with:
107 |           version: latest
108 | 
109 |       - name: Build WASM
110 |         run: ./build_wasm.sh
111 | 
112 |       - name: Upload WASM artifacts
113 |         uses: actions/upload-artifact@v4
114 |         with:
115 |           name: wasm-module
116 |           path: |
117 |             docs/ipv6-parse.js
118 |             docs/ipv6-parse-api.js
119 |             docs/ipv6-parse-api.d.ts
120 |             docs/index.html
121 |             docs/README.md
122 | 
123 |   create-release:
124 |     name: Create GitHub Release
125 |     needs: [build-matrix, build-wasm]
126 |     runs-on: ubuntu-latest
127 |     steps:
128 |       - name: Checkout code
129 |         uses: actions/checkout@v4
130 | 
131 |       - name: Download all artifacts
132 |         uses: actions/download-artifact@v4
133 |         with:
134 |           path: artifacts
135 | 
136 |       - name: Prepare release assets
137 |         run: |
138 |           mkdir -p release
139 |           # Copy Linux packages
140 |           find artifacts/linux-packages-* -type f \( -name "*.deb" -o -name "*.rpm" -o -name "*.tar.gz" \) -exec cp {} release/ \;
141 |           # Copy WASM module
142 |           cd artifacts/wasm-module
143 |           zip -r ../../release/ipv6-parse-wasm.zip *
144 |           cd ../..
145 | 
146 |       - name: Extract version from tag
147 |         id: version
148 |         run: |
149 |           if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
150 |             TAG="${{ github.event.inputs.tag }}"
151 |           else
152 |             TAG="${{ github.ref_name }}"
153 |           fi
154 |           VERSION=${TAG#v}
155 |           echo "version=$VERSION" >> $GITHUB_OUTPUT
156 |           echo "tag=$TAG" >> $GITHUB_OUTPUT
157 | 
158 |       - name: Generate release notes
159 |         run: |
160 |           cat > release_notes.md << 'EOF'
161 |           ## Release ${{ steps.version.outputs.version }}
162 | 
163 |           ### Features
164 |           - Full RFC compliance (RFC 4291, RFC 5952, RFC 4007)
165 |           - Support for CIDR masks, ports, and zone IDs
166 |           - IPv4-mapped and IPv4-compatible addresses
167 |           - High-performance C implementation
168 |           - WebAssembly support for browsers
169 |           - NPM package for Node.js
170 |           - Native packages for Linux (deb/rpm)
171 | 
172 |           ### Installation
173 | 
174 |           **Debian/Ubuntu:**
175 |           ```bash
176 |           wget https://github.com/jrepp/ipv6-parse/releases/download/${{ steps.version.outputs.tag }}/ipv6-parse-${{ steps.version.outputs.version }}-Linux.deb
177 |           sudo dpkg -i ipv6-parse-${{ steps.version.outputs.version }}-Linux.deb
178 |           ```
179 | 
180 |           **Fedora/RHEL/CentOS:**
181 |           ```bash
182 |           wget https://github.com/jrepp/ipv6-parse/releases/download/${{ steps.version.outputs.tag }}/ipv6-parse-${{ steps.version.outputs.version }}-Linux.rpm
183 |           sudo rpm -i ipv6-parse-${{ steps.version.outputs.version }}-Linux.rpm
184 |           ```
185 | 
186 |           **NPM:**
187 |           ```bash
188 |           npm install ipv6-parse
189 |           ```
190 | 
191 |           **WebAssembly:**
192 |           Download `ipv6-parse-wasm.zip` and extract to your project.
193 | 
194 |           ### Documentation
195 |           - [README](https://github.com/jrepp/ipv6-parse#readme)
196 |           - [WASM Guide](https://github.com/jrepp/ipv6-parse/blob/master/README_WASM.md)
197 |           - [NPM Guide](https://github.com/jrepp/ipv6-parse/blob/master/README_NPM.md)
198 |           - [Interactive Demo](https://jrepp.github.io/ipv6-parse/)
199 |           EOF
200 | 
201 |       - name: Create Release
202 |         uses: softprops/action-gh-release@v1
203 |         with:
204 |           tag_name: ${{ steps.version.outputs.tag }}
205 |           name: Release ${{ steps.version.outputs.version }}
206 |           body_path: release_notes.md
207 |           draft: false
208 |           prerelease: false
209 |           files: release/*
210 |         env:
211 |           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
212 | 
213 |   publish-npm:
214 |     name: Publish to NPM
215 |     needs: [build-wasm, create-release]
216 |     runs-on: ubuntu-latest
217 |     if: startsWith(github.ref, 'refs/tags/v')
218 |     steps:
219 |       - name: Checkout code
220 |         uses: actions/checkout@v4
221 | 
222 |       - name: Setup Node.js
223 |         uses: actions/setup-node@v4
224 |         with:
225 |           node-version: '18'
226 |           registry-url: 'https://registry.npmjs.org'
227 | 
228 |       - name: Download WASM artifact
229 |         uses: actions/download-artifact@v4
230 |         with:
231 |           name: wasm-module
232 |           path: docs
233 | 
234 |       - name: Install dependencies
235 |         run: npm install
236 | 
237 |       - name: Run linting
238 |         run: npm run lint
239 | 
240 |       - name: Run tests
241 |         run: npm run test
242 | 
243 |       - name: Publish to NPM
244 |         run: npm publish
245 |         env:
246 |           NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
247 | 
248 |   deploy-pages:
249 |     name: Deploy to GitHub Pages
250 |     needs: [build-wasm, create-release]
251 |     runs-on: ubuntu-latest
252 |     environment:
253 |       name: github-pages
254 |       url: ${{ steps.deployment.outputs.page_url }}
255 |     steps:
256 |       - name: Checkout code
257 |         uses: actions/checkout@v4
258 | 
259 |       - name: Download WASM artifact
260 |         uses: actions/download-artifact@v4
261 |         with:
262 |           name: wasm-module
263 |           path: docs
264 | 
265 |       - name: Setup Pages
266 |         uses: actions/configure-pages@v4
267 | 
268 |       - name: Upload artifact
269 |         uses: actions/upload-pages-artifact@v3
270 |         with:
271 |           path: 'docs'
272 | 
273 |       - name: Deploy to GitHub Pages
274 |         id: deployment
275 |         uses: actions/deploy-pages@v4
276 | 
277 |   publish-conan:
278 |     name: Publish to Conan
279 |     needs: [build-matrix, create-release]
280 |     runs-on: ubuntu-latest
281 |     if: startsWith(github.ref, 'refs/tags/v')
282 |     strategy:
283 |       matrix:
284 |         include:
285 |           - os: Linux
286 |             arch: x86_64
287 |           - os: Linux
288 |             arch: armv8
289 |     steps:
290 |       - name: Checkout code
291 |         uses: actions/checkout@v4
292 | 
293 |       - name: Set up Python
294 |         uses: actions/setup-python@v5
295 |         with:
296 |           python-version: '3.11'
297 | 
298 |       - name: Install Conan
299 |         run: |
300 |           pip install conan
301 |           conan profile detect --force
302 | 
303 |       - name: Extract version from tag
304 |         id: version
305 |         run: |
306 |           TAG="${{ github.ref_name }}"
307 |           VERSION=${TAG#v}
308 |           echo "version=$VERSION" >> $GITHUB_OUTPUT
309 | 
310 |       - name: Create Conan package
311 |         run: |
312 |           conan create . --build=missing
313 | 
314 |       - name: Upload Conan package artifact
315 |         uses: actions/upload-artifact@v4
316 |         with:
317 |           name: conan-package-${{ matrix.os }}-${{ matrix.arch }}
318 |           path: ~/.conan2/p/
319 | 
320 |       # Publish to a custom Conan remote (requires CONAN_REMOTE_URL and CONAN_API_KEY secrets)
321 |       - name: Publish to Conan remote
322 |         if: ${{ vars.CONAN_REMOTE_URL != '' }}
323 |         env:
324 |           CONAN_REMOTE_URL: ${{ vars.CONAN_REMOTE_URL }}
325 |           CONAN_API_KEY: ${{ secrets.CONAN_API_KEY }}
326 |         run: |
327 |           conan remote add publish "$CONAN_REMOTE_URL"
328 |           conan remote login publish -p "$CONAN_API_KEY" ci
329 |           conan upload "ipv6-parse/${{ steps.version.outputs.version }}" -r publish --confirm
330 | 


--------------------------------------------------------------------------------
/ROADMAP_RELEASES.md:
--------------------------------------------------------------------------------
  1 | # Release Build Infrastructure Roadmap
  2 | 
  3 | This document outlines the plan to add comprehensive build and release infrastructure to ipv6-parse.
  4 | 
  5 | ## Goals
  6 | 
  7 | 1. **WebAssembly Support** - Enable browser-based IPv6 parsing
  8 | 2. **Native Libraries** - Build static and shared libraries for all platforms
  9 | 3. **Package Distribution** - Distribute via deb, rpm, and npm
 10 | 4. **Automated Releases** - CI/CD pipeline for automatic releases
 11 | 5. **GitHub Pages Demo** - Interactive demo site
 12 | 
 13 | ## PR Strategy
 14 | 
 15 | To maintain code quality and ease review, this work will be split into multiple focused PRs:
 16 | 
 17 | ### PR #1: WebAssembly Build Support ✅ (CURRENT)
 18 | 
 19 | **Branch**: `wasm-build-target`
 20 | 
 21 | **Files Added**:
 22 | - `cmake/emscripten.cmake` - Emscripten CMake toolchain
 23 | - `ipv6_wasm.c` - WASM-specific C bindings
 24 | - `build_wasm.sh` - Build script for WASM
 25 | - `docs/index.html` - Interactive demo page
 26 | - `docs/ipv6-parse.js` - (Generated by build)
 27 | - `README_WASM.md` - WASM documentation
 28 | 
 29 | **Description**:
 30 | Adds WebAssembly build target using Emscripten, enabling ipv6-parse to run in web browsers. Includes an interactive demo page inspired by v6decode.com for testing IPv6 addresses.
 31 | 
 32 | **Testing**:
 33 | 1. Install Emscripten SDK
 34 | 2. Run `./build_wasm.sh`
 35 | 3. Test locally: `python3 -m http.server --directory docs`
 36 | 4. Open http://localhost:8000
 37 | 
 38 | **Benefits**:
 39 | - No server-side processing required for IPv6 parsing
 40 | - Interactive testing interface
 41 | - Foundation for GitHub Pages deployment
 42 | 
 43 | ---
 44 | 
 45 | ### PR #2: Shared Library Support
 46 | 
 47 | **Branch**: `shared-library-support` (future)
 48 | 
 49 | **Changes**:
 50 | - Update `CMakeLists.txt` to build both static and shared libraries
 51 | - Add library versioning (SONAME)
 52 | - Create pkg-config `.pc` file for library discovery
 53 | - Update installation targets
 54 | 
 55 | **Files Modified**:
 56 | - `CMakeLists.txt`
 57 | 
 58 | **Files Added**:
 59 | - `ipv6-parse.pc.in` - pkg-config template
 60 | 
 61 | **Description**:
 62 | Extends CMake configuration to build shared libraries (`.so`, `.dylib`, `.dll`) in addition to static libraries, with proper versioning and pkg-config support.
 63 | 
 64 | **Testing**:
 65 | ```bash
 66 | mkdir build && cd build
 67 | cmake -DBUILD_SHARED_LIBS=ON ..
 68 | make
 69 | sudo make install
 70 | pkg-config --cflags --libs ipv6-parse
 71 | ```
 72 | 
 73 | ---
 74 | 
 75 | ### PR #3: NPM Package
 76 | 
 77 | **Branch**: `npm-package` (future)
 78 | 
 79 | **Files Added**:
 80 | - `package.json` - NPM package configuration
 81 | - `.npmignore` - Files to exclude from NPM
 82 | - `binding.gyp` - Node.js native module build config (alternative to WASM)
 83 | - `index.js` - Node.js entry point
 84 | - `README_NPM.md` - NPM-specific documentation
 85 | 
 86 | **Description**:
 87 | Creates an NPM package for Node.js applications, providing both WASM and native bindings (via node-gyp) for optimal performance.
 88 | 
 89 | **Package Features**:
 90 | - Dual WASM/native support (WASM for portability, native for performance)
 91 | - TypeScript definitions
 92 | - Zero dependencies (for WASM version)
 93 | - CommonJS and ESM support
 94 | 
 95 | **Testing**:
 96 | ```bash
 97 | npm pack
 98 | npm install ./ipv6-parse-1.2.1.tgz
 99 | node -e "const ipv6 = require('ipv6-parse'); console.log(ipv6.parse('::1'))"
100 | ```
101 | 
102 | **Publishing**:
103 | ```bash
104 | npm publish
105 | ```
106 | 
107 | ---
108 | 
109 | ### PR #4: Linux Package Distribution (deb/rpm)
110 | 
111 | **Branch**: `linux-packages` (future)
112 | 
113 | **Changes**:
114 | - Add CPack configuration to `CMakeLists.txt`
115 | - Create Debian package metadata
116 | - Create RPM spec file
117 | 
118 | **Files Added**:
119 | - `debian/control` - Debian package metadata
120 | - `debian/copyright` - License information
121 | - `debian/changelog` - Package changelog
122 | - `ipv6-parse.spec` - RPM spec file
123 | 
124 | **Files Modified**:
125 | - `CMakeLists.txt` - Add CPack configuration
126 | 
127 | **Description**:
128 | Adds CPack configuration to generate native Linux packages (deb for Debian/Ubuntu, rpm for Fedora/RHEL).
129 | 
130 | **Package Contents**:
131 | - Runtime library: `libipv6-parse.so.1.2.1`
132 | - Development files: `libipv6-parse-dev`
133 | - Headers: `/usr/include/ipv6.h`
134 | - pkg-config: `/usr/lib/pkgconfig/ipv6-parse.pc`
135 | 
136 | **Testing**:
137 | ```bash
138 | # Build packages
139 | mkdir build && cd build
140 | cmake -DCPACK_GENERATOR="DEB;RPM" ..
141 | make package
142 | 
143 | # Install and test
144 | sudo dpkg -i ipv6-parse-1.2.1-Linux.deb
145 | sudo rpm -i ipv6-parse-1.2.1-Linux.rpm
146 | ```
147 | 
148 | ---
149 | 
150 | ### PR #5: GitHub Actions Release Pipeline
151 | 
152 | **Branch**: `release-automation` (future)
153 | 
154 | **Files Added**:
155 | - `.github/workflows/release.yml` - Release workflow
156 | - `.github/workflows/build-matrix.yml` - Multi-platform build matrix
157 | - `scripts/prepare-release.sh` - Release preparation script
158 | - `scripts/build-packages.sh` - Package build script
159 | 
160 | **Description**:
161 | Implements automated release pipeline using GitHub Actions that:
162 | 
163 | 1. **Triggered on**:
164 |    - Git tags matching `v*.*.*`
165 |    - Manual workflow dispatch
166 | 
167 | 2. **Build matrix**:
168 |    - **Linux**: Ubuntu 22.04, 24.04 (gcc, clang)
169 |    - **macOS**: Latest (AppleClang)
170 |    - **Windows**: Latest (MSVC)
171 |    - **WASM**: Emscripten latest
172 | 
173 | 3. **Artifacts generated**:
174 |    - Static libraries (`.a`, `.lib`)
175 |    - Shared libraries (`.so`, `.dylib`, `.dll`)
176 |    - WASM module (`ipv6-parse.js`)
177 |    - Linux packages (`.deb`, `.rpm`)
178 |    - Source tarball
179 | 
180 | 4. **Publish**:
181 |    - GitHub Release with all artifacts
182 |    - NPM package (if version tag)
183 |    - Update GitHub Pages (WASM demo)
184 | 
185 | **Workflow Steps**:
186 | ```yaml
187 | 1. Checkout code
188 | 2. Set up build environment
189 | 3. Run tests
190 | 4. Build for platform
191 | 5. Generate packages
192 | 6. Upload artifacts
193 | 7. Create GitHub Release
194 | 8. Publish to NPM (if tag)
195 | 9. Deploy to GitHub Pages
196 | ```
197 | 
198 | **Testing**:
199 | ```bash
200 | # Test locally with act
201 | act -j release
202 | 
203 | # Or create a test tag
204 | git tag v1.2.1-rc1
205 | git push origin v1.2.1-rc1
206 | ```
207 | 
208 | ---
209 | 
210 | ### PR #6: Documentation Updates
211 | 
212 | **Branch**: `documentation-updates` (future)
213 | 
214 | **Files Modified**:
215 | - `README.md` - Add build/install instructions for all platforms
216 | 
217 | **Files Added**:
218 | - `docs/BUILDING.md` - Comprehensive build guide
219 | - `docs/PACKAGING.md` - Package maintainer guide
220 | - `docs/RELEASING.md` - Release process guide
221 | 
222 | **Description**:
223 | Updates all documentation to reflect new build options, installation methods, and package availability.
224 | 
225 | **Sections to Add**:
226 | - Installation instructions (npm, apt, yum, homebrew)
227 | - Building from source (all platforms)
228 | - Cross-compilation guide
229 | - Package maintainer documentation
230 | - Release checklist
231 | 
232 | ---
233 | 
234 | ## Implementation Timeline
235 | 
236 | ```mermaid
237 | gantt
238 |     title Release Infrastructure Implementation
239 |     dateFormat  YYYY-MM-DD
240 |     section WASM
241 |     PR #1: WebAssembly        :done, 2025-01-01, 3d
242 |     section Libraries
243 |     PR #2: Shared Libraries   :active, 2025-01-04, 2d
244 |     section Distribution
245 |     PR #3: NPM Package        :2025-01-06, 3d
246 |     PR #4: Linux Packages     :2025-01-09, 2d
247 |     section Automation
248 |     PR #5: CI/CD Pipeline     :2025-01-11, 4d
249 |     section Documentation
250 |     PR #6: Documentation      :2025-01-15, 2d
251 | ```
252 | 
253 | ## Dependencies
254 | 
255 | - **PR #1** → Independent (can merge first)
256 | - **PR #2** → Independent (can merge in any order)
257 | - **PR #3** → Depends on PR #1 (needs WASM build)
258 | - **PR #4** → Depends on PR #2 (needs shared library)
259 | - **PR #5** → Depends on PR #1-4 (needs all build targets)
260 | - **PR #6** → Depends on PR #1-5 (documents everything)
261 | 
262 | ## Deployment Plan
263 | 
264 | ### Phase 1: WASM (Immediate)
265 | 1. Merge PR #1
266 | 2. Enable GitHub Pages on `master` branch, `/docs` folder
267 | 3. Demo available at https://jrepp.github.io/ipv6-parse/
268 | 
269 | ### Phase 2: Native Libraries (Week 1)
270 | 1. Merge PR #2
271 | 2. Users can build shared libraries locally
272 | 
273 | ### Phase 3: Package Distribution (Week 2)
274 | 1. Merge PR #3 and #4
275 | 2. First manual release with packages
276 | 3. Publish initial NPM package
277 | 
278 | ### Phase 4: Automation (Week 3)
279 | 1. Merge PR #5
280 | 2. Create `v1.3.0` tag
281 | 3. Automated release with all artifacts
282 | 
283 | ### Phase 5: Documentation (Week 4)
284 | 1. Merge PR #6
285 | 2. Announce v1.3.0 release with full feature set
286 | 
287 | ## Version Strategy
288 | 
289 | - **v1.2.1** (current) - Base library
290 | - **v1.3.0** - WASM support + shared libraries
291 | - **v1.4.0** - Package distribution (npm, deb, rpm)
292 | - **v1.5.0** - Full CI/CD automation
293 | 
294 | ## Success Metrics
295 | 
296 | 1. ✅ WASM demo accessible via GitHub Pages
297 | 2. ⬜ Package available on NPM
298 | 3. ⬜ Packages available on Ubuntu/Debian (via PPA or direct download)
299 | 4. ⬜ Packages available on Fedora/RHEL (via COPR or direct download)
300 | 5. ⬜ Automated releases on every tag
301 | 6. ⬜ Multi-platform CI testing
302 | 
303 | ## Questions & Discussion
304 | 
305 | ### Should we support Homebrew?
306 | 
307 | Yes, but as a future enhancement. Creating a Homebrew formula is straightforward once we have stable releases.
308 | 
309 | ```ruby
310 | # ipv6-parse.rb (future)
311 | class Ipv6Parse < Formula
312 |   desc "IPv6/IPv4 address parser with full RFC compliance"
313 |   homepage "https://github.com/jrepp/ipv6-parse"
314 |   url "https://github.com/jrepp/ipv6-parse/archive/v1.3.0.tar.gz"
315 |   sha256 "..."
316 |   license "MIT"
317 | 
318 |   depends_on "cmake" => :build
319 | 
320 |   def install
321 |     system "cmake", ".", *std_cmake_args
322 |     system "make", "install"
323 |   end
324 | 
325 |   test do
326 |     # Add test
327 |   end
328 | end
329 | ```
330 | 
331 | ### Should we support Conan?
332 | 
333 | Potentially, as it's popular for C++ projects. This would be PR #7.
334 | 
335 | ### Should we publish to vcpkg?
336 | 
337 | Yes, vcpkg is very popular for Windows/Visual Studio users. This would be PR #8.
338 | 
339 | ## Current Status
340 | 
341 | - ✅ **PR #1 (WASM)**: Complete and in PR #24
342 | - ✅ **PR #2 (Shared Libraries)**: Complete and in PR #24
343 | - ✅ **PR #3 (NPM Package)**: Complete and in PR #24
344 | - ✅ **PR #4 (Linux Packages)**: Complete and in PR #24
345 | - ✅ **PR #5 (GitHub Actions CI/CD)**: Complete and in PR #24
346 | - ✅ **PR #6 (Documentation)**: Complete and in PR #24
347 | - 🚀 **All roadmap items implemented in single comprehensive PR!**
348 | 
349 | ## Next Steps
350 | 
351 | 1. ✅ Merge PR #24 (Complete infrastructure overhaul)
352 | 2. ⏳ Test GitHub Pages deployment
353 | 3. ⏳ Publish to NPM
354 | 4. 🎯 Add future enhancements: Homebrew, Conan, vcpkg
355 | 
356 | ## Contributing
357 | 
358 | Feedback on this roadmap is welcome! Please open an issue or PR to suggest changes.
359 | 


--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
  1 | cmake_minimum_required(VERSION 3.12)
  2 | 
  3 | find_program(CCACHE_PROGRAM ccache)
  4 | if(CCACHE_PROGRAM)
  5 |     message(STATUS "IPv6-Parse: Found ccache package... Activating...")
  6 |     set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
  7 | endif()
  8 | 
  9 | set(LIB_MAJOR_VERSION "1")
 10 | set(LIB_MINOR_VERSION "2")
 11 | set(LIB_PATCH_VERSION "1")
 12 | set(LIB_VERSION_STRING "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_PATCH_VERSION}")
 13 | 
 14 | if(CMAKE_VERSION VERSION_LESS 3.0)
 15 |     project(ipv6 C)
 16 |     enable_language(C)
 17 |     enable_language(CXX)
 18 | else()
 19 |     cmake_policy(SET CMP0003 NEW)
 20 |     cmake_policy(SET CMP0048 NEW)
 21 |     project(ipv6 VERSION "${LIB_VERSION_STRING}" LANGUAGES C CXX)
 22 | endif()
 23 | 
 24 | ## This section describes our general CMake setup options
 25 | set_property(GLOBAL PROPERTY USE_FOLDERS ON)
 26 | set(CMAKE_POSITION_INDEPENDENT_CODE ON)
 27 | set(CMAKE_SKIP_INSTALL_RULES OFF FORCE)
 28 | set(CMAKE_SKIP_PACKAGE_ALL_DEPENDENCY ON FORCE)
 29 | set(CMAKE_SUPPRESS_REGENERATION ON)
 30 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
 31 | 
 32 | # Build with c++17 support.
 33 | set(CMAKE_CXX_STANDARD 17)
 34 | set(CMAKE_CXX_STANDARD_REQUIRED ON)
 35 | set(CMAKE_CXX_EXTENSIONS OFF)
 36 | 
 37 | ## Build options for library type
 38 | option(BUILD_SHARED_LIBS "Build shared libraries instead of static" OFF)
 39 | if(NOT BUILD_SHARED_LIBS)
 40 |     set(STATIC ON CACHE BOOL FORCE "Link libraries statically? Forced to ON")
 41 |     add_definitions(-DSTATICLIB)
 42 | endif()
 43 | 
 44 | SET(PARSE_TRACE 0 CACHE BOOL "Enable tracing of address parsing")
 45 | SET(ENABLE_COVERAGE 0 CACHE BOOL "Enable code coverage reporting")
 46 | 
 47 | option(IPV6_PARSE_LIBRARY_ONLY "Build Only the Library" ON)
 48 | if(DEFINED ENV{IPV6_PARSE_LIBRARY_ONLY})
 49 |   set(IPV6_PARSE_LIBRARY_ONLY $ENV{IPV6_PARSE_LIBRARY_ONLY})
 50 | endif()
 51 | 
 52 | # Include header checks
 53 | include (CheckIncludeFiles)
 54 | CHECK_INCLUDE_FILES(malloc.h HAVE_MALLOC_H)
 55 | CHECK_INCLUDE_FILES(alloca.h HAVE_ALLOCA_H)
 56 | CHECK_INCLUDE_FILES(string.h HAVE_STRING_H)
 57 | CHECK_INCLUDE_FILES(stdio.h HAVE_STDIO_H)
 58 | CHECK_INCLUDE_FILES(stdarg.h HAVE_STDARG_H)
 59 | 
 60 | configure_file(ipv6_config.h.in ipv6_config.h)
 61 | set(IPV6_CONFIG_HEADER_PATH ${CMAKE_CURRENT_BINARY_DIR})
 62 | message("-- Including ipv6_config.h from ${IPV6_CONFIG_HEADER_PATH}")
 63 | 
 64 | # Use bin as the directory for all executables.
 65 | # This will make protoc easy to find.
 66 | set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
 67 | set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
 68 | 
 69 | # Windows builds subdirectories Debug/Release.
 70 | # These variables will overwrite that and put binaries in bin.
 71 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${PROJECT_BINARY_DIR}/bin)
 72 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${PROJECT_BINARY_DIR}/bin)
 73 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_BINARY_DIR}/bin)
 74 | 
 75 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${PROJECT_BINARY_DIR}/bin)
 76 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${PROJECT_BINARY_DIR}/bin)
 77 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_BINARY_DIR}/bin)
 78 | 
 79 | # Allow creating filters for projects in visual studio.
 80 | set_property(GLOBAL PROPERTY USE_FOLDERS ON)
 81 | 
 82 | file(GLOB ipv6_sources "ipv6.h" "ipv6.c" ${IPV6_CONFIG_HEADER_PATH}/ipv6_config.h)
 83 | 
 84 | option(IPV6_CONAN_BUILD "Building with Conan package manager" OFF)
 85 | 
 86 | if (MSVC)
 87 |     if (IPV6_CONAN_BUILD)
 88 |         # When building with Conan, use minimal flags and let Conan control runtime
 89 |         # Disable C4711 (auto inline) and C5045 (Spectre) warnings
 90 |         set(ipv6_target_compile_flags "/W4 /wd4711 /wd5045")
 91 |     else ()
 92 |         set(ipv6_target_compile_flags "/MTd /Wall /WX /wd5045 /ZI /Od /D_NO_CRT_STDIO_INLINE=1")
 93 |     endif ()
 94 | else ()
 95 |     set(ipv6_target_compile_flags "-Wall -Werror -Wno-long-long -pedantic -std=c99 -Wno-unused-but-set-variable")
 96 |     if (ENABLE_COVERAGE)
 97 |         set(ipv6_target_compile_flags "${ipv6_target_compile_flags} --coverage -O0")
 98 |         set(ipv6_target_link_flags "--coverage")
 99 |     endif ()
100 | endif ()
101 | 
102 | if (NOT IPV6_PARSE_LIBRARY_ONLY)
103 |     CHECK_INCLUDE_FILES(winsock2.h HAVE_WINSOCK_2_H)
104 |     CHECK_INCLUDE_FILES(sys/socket.h HAVE_SYS_SOCKET_H)
105 |     CHECK_INCLUDE_FILES(netinet/in.h HAVE_NETINET_IN_H)
106 |     CHECK_INCLUDE_FILES(arpa/inet.h HAVE_ARPA_INET_H)
107 |     CHECK_INCLUDE_FILES(ws2tcpip.h HAVE_WS_2_TCPIP_H)
108 |     CHECK_INCLUDE_FILES(assert.h HAVE_ASSERT_H)
109 | 
110 |     configure_file(ipv6_test_config.h.in ipv6_test_config.h)
111 |     set(IPV6_TEST_CONFIG_HEADER_PATH ${CMAKE_CURRENT_BINARY_DIR})
112 | 
113 |     add_executable(ipv6-test ${ipv6_sources} "test.c")
114 |     add_executable(ipv6-fuzz ${ipv6_sources} "fuzz.c")
115 |     add_executable(ipv6-cmd ${ipv6_sources} "cmdline.c")
116 |     add_executable(ipv6-test-extended ${ipv6_sources} "test_extended.c")
117 | 
118 |     set_target_properties(ipv6-test PROPERTIES COMPILE_FLAGS ${ipv6_target_compile_flags})
119 |     set_target_properties(ipv6-fuzz PROPERTIES COMPILE_FLAGS ${ipv6_target_compile_flags})
120 |     set_target_properties(ipv6-cmd PROPERTIES COMPILE_FLAGS ${ipv6_target_compile_flags})
121 |     set_target_properties(ipv6-test-extended PROPERTIES COMPILE_FLAGS ${ipv6_target_compile_flags})
122 | 
123 |     if (ENABLE_COVERAGE)
124 |         set_target_properties(ipv6-test PROPERTIES LINK_FLAGS ${ipv6_target_link_flags})
125 |         set_target_properties(ipv6-fuzz PROPERTIES LINK_FLAGS ${ipv6_target_link_flags})
126 |         set_target_properties(ipv6-cmd PROPERTIES LINK_FLAGS ${ipv6_target_link_flags})
127 |         set_target_properties(ipv6-test-extended PROPERTIES LINK_FLAGS ${ipv6_target_link_flags})
128 |     endif ()
129 | 
130 |     target_include_directories(ipv6-test PRIVATE ${IPV6_CONFIG_HEADER_PATH} ${IPV6_TEST_CONFIG_HEADER_PATH})
131 |     target_include_directories(ipv6-fuzz PRIVATE ${IPV6_CONFIG_HEADER_PATH} ${IPV6_TEST_CONFIG_HEADER_PATH})
132 |     target_include_directories(ipv6-cmd PRIVATE ${IPV6_CONFIG_HEADER_PATH})
133 |     target_include_directories(ipv6-test-extended PRIVATE ${IPV6_CONFIG_HEADER_PATH} ${IPV6_TEST_CONFIG_HEADER_PATH})
134 | 
135 |     if (MSVC)
136 |         target_link_libraries(ipv6-test ws2_32)
137 |         target_link_libraries(ipv6-fuzz ws2_32)
138 |         target_link_libraries(ipv6-cmd ws2_32)
139 |         target_link_libraries(ipv6-test-extended ws2_32)
140 |     endif ()
141 |     enable_testing()
142 |     add_test(NAME verification COMMAND ipv6-test)
143 |     add_test(NAME fuzz COMMAND ipv6-fuzz 100)
144 |     add_test(NAME extended COMMAND ipv6-test-extended)
145 | endif ()
146 | 
147 | add_library(ipv6-parse ${ipv6_sources})
148 | target_include_directories(ipv6-parse PUBLIC
149 |     $
150 |     $
151 |     $
152 | )
153 | set_target_properties(ipv6-parse PROPERTIES
154 |     COMPILE_FLAGS ${ipv6_target_compile_flags}
155 |     VERSION ${LIB_VERSION_STRING}
156 |     SOVERSION ${LIB_MAJOR_VERSION}
157 |     PUBLIC_HEADER "ipv6.h;${IPV6_CONFIG_HEADER_PATH}/ipv6_config.h"
158 | )
159 | 
160 | if (PARSE_TRACE)
161 |     message("Address parse tracing enabled")
162 |     set_target_properties(ipv6-parse PROPERTIES COMPILE_DEFINITIONS PARSE_TRACE=1)
163 | 		set_target_properties(ipv6-test PROPERTIES COMPILE_DEFINITIONS PARSE_TRACE=1)
164 | 		set_target_properties(ipv6-cmd PROPERTIES COMPILE_DEFINITIONS PARSE_TRACE=1)
165 | endif ()
166 | 
167 | foreach(flag_var
168 |         CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE
169 |         CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO
170 |         CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
171 |         CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
172 |     string(REGEX REPLACE "/RTC[^ ]*" "" ${flag_var} "${${flag_var}}")
173 |     string(REGEX REPLACE "/Od" "" ${flag_var} "${${flag_var}}")
174 | endforeach(flag_var)
175 | 
176 | ## Installation configuration
177 | include(GNUInstallDirs)
178 | 
179 | # Install library
180 | install(TARGETS ipv6-parse
181 |     EXPORT ipv6-parse-targets
182 |     LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
183 |     ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
184 |     RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
185 |     PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
186 | )
187 | 
188 | # Configure and install pkg-config file
189 | configure_file(
190 |     ${CMAKE_CURRENT_SOURCE_DIR}/ipv6-parse.pc.in
191 |     ${CMAKE_CURRENT_BINARY_DIR}/ipv6-parse.pc
192 |     @ONLY
193 | )
194 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ipv6-parse.pc
195 |     DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
196 | )
197 | 
198 | # Install CMake package configuration
199 | install(EXPORT ipv6-parse-targets
200 |     FILE ipv6-parse-targets.cmake
201 |     NAMESPACE ipv6-parse::
202 |     DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ipv6-parse
203 | )
204 | 
205 | # Create and install package config file
206 | include(CMakePackageConfigHelpers)
207 | configure_package_config_file(
208 |     ${CMAKE_CURRENT_SOURCE_DIR}/cmake/ipv6-parse-config.cmake.in
209 |     ${CMAKE_CURRENT_BINARY_DIR}/ipv6-parse-config.cmake
210 |     INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ipv6-parse
211 | )
212 | write_basic_package_version_file(
213 |     ${CMAKE_CURRENT_BINARY_DIR}/ipv6-parse-config-version.cmake
214 |     VERSION ${LIB_VERSION_STRING}
215 |     COMPATIBILITY SameMajorVersion
216 | )
217 | install(FILES
218 |     ${CMAKE_CURRENT_BINARY_DIR}/ipv6-parse-config.cmake
219 |     ${CMAKE_CURRENT_BINARY_DIR}/ipv6-parse-config-version.cmake
220 |     DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ipv6-parse
221 | )
222 | 
223 | ## CPack configuration for package generation
224 | set(CPACK_PACKAGE_NAME "ipv6-parse")
225 | set(CPACK_PACKAGE_VENDOR "Jacob Repp")
226 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "IPv6/IPv4 address parser with full RFC compliance")
227 | set(CPACK_PACKAGE_DESCRIPTION "High-performance IPv6/IPv4 address parser supporting RFC 4291, RFC 5952, and RFC 4007. Includes support for CIDR masks, ports, zone IDs, and IPv4 embedding.")
228 | set(CPACK_PACKAGE_VERSION_MAJOR ${LIB_MAJOR_VERSION})
229 | set(CPACK_PACKAGE_VERSION_MINOR ${LIB_MINOR_VERSION})
230 | set(CPACK_PACKAGE_VERSION_PATCH ${LIB_PATCH_VERSION})
231 | set(CPACK_PACKAGE_VERSION ${LIB_VERSION_STRING})
232 | set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/jrepp/ipv6-parse")
233 | set(CPACK_PACKAGE_CONTACT "Jacob Repp ")
234 | set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
235 | set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
236 | 
237 | # Debian package configuration
238 | set(CPACK_DEBIAN_PACKAGE_SECTION "libs")
239 | set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
240 | set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.17)")
241 | set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Jacob Repp ")
242 | set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
243 | 
244 | # RPM package configuration
245 | set(CPACK_RPM_PACKAGE_LICENSE "MIT")
246 | set(CPACK_RPM_PACKAGE_GROUP "System Environment/Libraries")
247 | set(CPACK_RPM_PACKAGE_REQUIRES "glibc >= 2.17")
248 | set(CPACK_RPM_FILE_NAME RPM-DEFAULT)
249 | 
250 | # Source package configuration
251 | set(CPACK_SOURCE_GENERATOR "TGZ;ZIP")
252 | set(CPACK_SOURCE_IGNORE_FILES
253 |     "/\\.git/"
254 |     "/\\.github/"
255 |     "/\\.vscode/"
256 |     "/\\.idea/"
257 |     "/build/"
258 |     "/build_wasm/"
259 |     "/build_test/"
260 |     "\\.swp$"
261 |     "\\.swo$"
262 |     "~$"
263 |     "\\.DS_Store$"
264 | )
265 | 
266 | include(CPack)
267 | 


--------------------------------------------------------------------------------